├── .gitignore ├── LICENSE ├── README.md ├── pom.xml └── src └── main ├── java └── cn │ └── keking │ └── common │ └── log │ ├── Constants.java │ ├── LogAppenderAutoConfiguration.java │ ├── LogEnhancerBinder.java │ ├── appender │ ├── AliYunAppenderCallback.java │ ├── log4j2 │ │ └── Log4j2AliYunAppender.java │ └── logback │ │ └── LogbackAliYunAppender.java │ ├── config │ ├── CommonLogAppenderConfigLoader.java │ ├── CommonProjectConfig.java │ ├── LogAppenderConfig.java │ └── LogAppenderConfigLoader.java │ ├── enhancer │ ├── AbstractLogEnhancer.java │ ├── DefaultLog4j2Enhancer.java │ ├── DefaultLogbackEnhancer.java │ ├── LogEnhancer.java │ └── PatternLayoutEncoder.java │ └── utils │ └── LogEnvUtils.java └── resources ├── META-INF └── spring.factories └── aliyunlog.properties /.gitignore: -------------------------------------------------------------------------------- 1 | /target/ 2 | !.mvn/wrapper/maven-wrapper.jar 3 | 4 | ### STS ### 5 | .apt_generated 6 | .classpath 7 | .factorypath 8 | .project 9 | .settings 10 | .springBeans 11 | .sts4-cache 12 | 13 | ### IntelliJ IDEA ### 14 | .idea 15 | *.iws 16 | *.iml 17 | *.ipr 18 | 19 | ### NetBeans ### 20 | /nbproject/private/ 21 | /build/ 22 | /nbbuild/ 23 | /dist/ 24 | /nbdist/ 25 | /.nb-gradle/ 26 | 27 | # for Mac OS X System Files 28 | .DS_Store 29 | Thumbs.db 30 | 31 | # Compiled class file 32 | *.class 33 | 34 | # Log file 35 | *.log 36 | 37 | # BlueJ files 38 | *.ctxt 39 | 40 | # Mobile Tools for Java (J2ME) 41 | .mtj.tmp/ 42 | 43 | # Package Files # 44 | *.jar 45 | *.war 46 | *.ear 47 | *.zip 48 | *.tar.gz 49 | *.rar 50 | 51 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 52 | hs_err_pid* -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # kk-aliyun-log-appender 2 | `kk-aliyun-log-appender`能够自动感知应用环境内引入的的日志实现(Log4j2及Logback)并自动接入阿里云日志服务,在保留灵活配置的同时,大幅度简化开发配置,让应用适配日志服务更简单。 3 | 4 | ## 一、使用场景 5 | 1. 简化繁琐的阿里云日志服务接入的配置,`kk-aliyun-log-appender`已集成不同日志框架的阿里云日志服务接入实现,且能自动感知引入的日志实现,无需根据不同的日志实现去配置不同的接入逻辑。 6 | 2. 提供了默认配置,大多数场景下仅需要配置topic属性即可接入。 7 | 3. 提供了灵活的配置方案。 8 | 4. 接入了新版[Aliyun LOG Java Producer](https://github.com/aliyun/aliyun-log-java-producer) 9 | 10 | > Aliyun LOG Java Producer 是对老版 log-loghub-producer 的全面升级,解决了上一版存在的多个问题,包括网络异常情况下 CPU 占用率过高、关闭 producer 可能出现少量数据丢失等问题。另外,在容错方面也进行了加强,即使您存在误用,在资源、吞吐、隔离等方面都有较好的保证。 11 | 5. 对原有的log4j2.xml或logback.xml中的配置无影响 12 | 13 | ## 二、快速开始 14 | ### 2.1 springboot项目接入 15 | 1.添加maven依赖 16 | ```xml 17 | 18 | com.github.kekingcn 19 | aliyunlog-spring-boot-starter 20 | 0.1-SNAPSHOT 21 | 22 | ``` 23 | 2.配置必备属性topic 24 |
25 | topic用以标记一批日志,例如可以通过配置topic属性来区分不同的应用及应用profile(kk-call-center-prod) 26 | ```properties 27 | aliyun.log.enable=true 28 | aliyun.log.topic=test-topic 29 | ``` 30 | 3.`kk-aliyun-log-appender`默认为所有的logger接入阿里云日志服务,且日志级别为logger上定义的日志级别。 31 |
32 | 4.启动应用
33 | 如果接入成功,则会打印如下log 34 | ``` 35 | [main] INFO cn.keking.common.log.LogAppenderAutoConfiguration - kk aliyun log appender has been successfully initialized 36 | ``` 37 | ### 2.2 spring mvc项目接入 38 | 和spring boot相似,需要在spring-xx.xml配置中添加类扫描即可 39 | ```xml 40 | 41 | ``` 42 | 43 | ## 三、配置参数 44 | 45 | 仅aliyun.log.topic为必填属性 46 | 47 | | 参数 | 类型 | 默认值 | 说明 | 48 | | :--- | :---: | :---: | --- | 49 | | aliyun.log.enable | boolean | false | 是否上传至阿里云日志 | 50 | | **aliyun.log.topic** | String | 无 | 日志主题,**必填** | 51 | | aliyun.log.charset | String | UTF-8 | 输出到日志服务的字符集,默认是 UTF-8 | 52 | | aliyun.log.timeZone | String | UTC | 输出到日志服务的时间的时区,默认是 UTC | 53 | | aliyun.log.timeFormat | String | yyyy-MM-dd'T'HH:mmZ | 输出到日志服务的时间的格式 | 54 | | aliyun.log.logger.filter | List | 空 | 日志logger过滤器 | 55 | | aliyun.log.pattern | String | %d{HH:mm:ss.SSS} \[%thread\] %-5level %logger{36} - %msg%n | 自定义上传日志pattern | 56 | | aliyun.log.project.name | String | kk-log-proj | 日志服务的 project 名 | 57 | | aliyun.log.project.logstore | String | apps-shanghai | 日志服务的 logstore 名 | 58 | | aliyun.log.project.endpoint | String | cn-shanghai-intranet.log.aliyuncs.com | 日志服务的 HTTP 地址 | 59 | | aliyun.log.project.accessKeyId | String | LTAIC***SO32Qy | 用户身份标识 | 60 | | aliyun.log.project.accessKeySecret | String | csvZDMv******DiyaTaRzal | 用户身份标识 | 61 | | aliyun.log.project.stsToken | String | null | 为RAM角色签发的STS Token来访问阿里云服务 | 62 | | aliyun.log.project.userAgent | String | aliyun-log-java-producer | userAgent | 63 | | aliyun.log.producer.totalSizeInBytes | int | 100 * 1024 * 1024 | 单个producer实例能缓存的日志大小上限,默认为100MB。 | 64 | | aliyun.log.producer.maxBlockMs | long | 60*1000 | 如果producer可用空间不足,调用者在send方法上的最大阻塞时间,默认为60秒。如果超过这个时间后所需空间仍无法得到满足,send方法会抛出TimeoutException。如果将该值设为0,当所需空间无法得到满足时,send方法会立即抛出TimeoutException。如果您希望send方法一直阻塞直到所需空间得到满足,可将该值设为负数。 | 65 | | aliyun.log.producer.ioThreadCount | int | availableProcessors | 执行日志发送任务的线程池大小,默认为可用处理器个数。 | 66 | | aliyun.log.producer.batchSizeThresholdInBytes | int | 512 * 1024 | 当一个ProducerBatch中缓存的日志大小大于等于batchSizeThresholdInBytes时,该batch将被发送,默认为512KB,最大可设置成5MB。 | 67 | | aliyun.log.producer.batchCountThreshold | int | 4096 | 当一个ProducerBatch中缓存的日志条数大于等于batchCountThreshold时,该batch将被发送,默认为4096,最大可设置成40960。 | 68 | | aliyun.log.producer.lingerMs | int | 2000 | 一个ProducerBatch从创建到可发送的逗留时间,默认为2秒,最小可设置成100毫秒。 | 69 | | aliyun.log.producer.retries | int | 10 | 如果某个ProducerBatch首次发送失败,能够对其重试的次数,默认为10次。如果retries小于等于0,该ProducerBatch首次发送失败后将直接进入失败队列。 | 70 | | aliyun.log.producer.maxReservedAttempts | int | 11 | 每个ProducerBatch每次被尝试发送都对应着一个Attempt,此参数用来控制返回给用户的attempt个数,默认只保留最近的11次attempt信息。该参数越大能让您追溯更多的信息,但同时也会消耗更多的内存。 | 71 | | aliyun.log.producer.baseRetryBackoffMs | long | 100 | 默认值 | 首次重试的退避时间,默认为100毫秒。Producer采样指数退避算法,第N次重试的计划等待时间为baseRetryBackoffMs*2^(N-1)。 | 72 | | aliyun.log.producer.maxRetryBackoffMs | long | 50 * 1000 | 重试的最大退避时间,默认为50秒。 | 73 | | aliyun.log.producer.adjustShardHash | boolean | true | 如果调用send方法时指定了shardHash,该参数用于控制是否需要对其进行调整,默认为true。 | 74 | | aliyun.log.producer.buckets | int | 64 | 当且仅当adjustShardHash为true时,该参数才生效。此时,producer会自动将shardHash重新分组,分组数量为buckets。如果两条数据的shardHash不同,它们是无法合并到一起发送的,会降低producer吞吐量。将shardHash重新分组后,能让数据有更多地机会被批量发送。该参数的取值范围是[1,256],且必须是2的整数次幂,默认为64。 | 75 | 参阅:[Aliyun LOG Java Producer](https://github.com/aliyun/aliyun-log-java-producer) 76 | 77 | ## 四、使用限制 78 | 1. jdk`1.8` 79 | 2. Spring`4`以上 80 | 3. 需要统一日志编程接口至slf4j 81 | 4. 应用内的日志框架logback `1.2.3`版本以上或log4j2 `2.0.2`版本以上(与原阿里SDK一致) 82 | 5. 如果已接入阿里SDK,则本日志组件将不会起任何作用 83 | 84 | ## 五、常见场景 85 | #### 5.1 开发环境不上传日志到阿里云 86 | 在application-dev.properties中配置关闭上传日志 87 | ```properties 88 | aliyun.log.enable=false 89 | ``` 90 | 该参数默认为false,在生产环境中应当设置enable=true 91 | #### 5.2 自定义logger上传日志 92 | 例如在log4j2中有如下的配置 93 | ```xml 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | ``` 106 | 如果想要达到只有自定义的**mylog1**的log上传至阿里云 107 | 可以在application.properties中配置 108 | ```properties 109 | aliyun.log.logger.filter=ROOT,mylog2 110 | ``` 111 | #### 5.3 关闭日志发送失败重试 112 | 可以在application.properties中配置 113 | ```properties 114 | aliyun.log.producer.reties=0 115 | ``` 116 | #### 5.4 配置上传日志pattern 117 | 可以在application.properties中配置 118 | ```properties 119 | aliyun.log.pattern=%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n 120 | ``` 121 | 格式参阅: 122 | [log4j2](https://logging.apache.org/log4j/2.x/manual/layouts.html) 123 | [logback](https://logback.qos.ch/manual/layouts.html) 124 | #### 5.5 整合skywalking traceId 125 | 已集成skywalking toolkit,log4j2请使用\[%traceId],logback请使用\[%tid] 126 | ```properties 127 | aliyun.log.pattern=%d{HH:mm:ss.SSS} [%traceId] [%t] %-5level %logger{36} - %msg%n 128 | ``` 129 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | com.github.kekingcn 8 | aliyunlog-spring-boot-starter 9 | 0.1-SNAPSHOT 10 | 11 | aliyunlog 快速接入的 spring boot starter组件 12 | 13 | 14 | UTF-8 15 | 1.8 16 | 1.8 17 | 4.3.19.RELEASE 18 | 1.5.16.RELEASE 19 | 1.9.2 20 | 0.2.0 21 | 0.6.31 22 | 2.5.0 23 | 1.2.3 24 | 1.7.25 25 | 2.13.3 26 | 2.9.9 27 | 5.0.0-alpha 28 | 29 | 30 | 31 | https://github.com/kekingcn/aliyunlog-spring-boot-starter.git 32 | 33 | 34 | https://github.com/kekingcn/aliyunlog-spring-boot-starter/issues 35 | 36 | 37 | 38 | wanglaomo 39 | wjlcoder@outlook.com 40 | https://github.com/wanglaomo 41 | 42 | 43 | kl 44 | 632104866@QQ.com 45 | https://github.com/klboke 46 | http://www.kailing.pub/ 47 | 48 | 49 | 50 | 51 | 52 | org.springframework 53 | spring-context 54 | ${spring-context.version} 55 | provided 56 | 57 | 58 | org.springframework 59 | spring-web 60 | ${spring-context.version} 61 | provided 62 | true 63 | 64 | 65 | javax.servlet 66 | javax.servlet-api 67 | 4.0.1 68 | provided 69 | true 70 | 71 | 72 | org.springframework.boot 73 | spring-boot 74 | ${spring-boot.version} 75 | provided 76 | 77 | 78 | 79 | 80 | ch.qos.logback 81 | logback-classic 82 | ${logback.version} 83 | provided 84 | 85 | 86 | 87 | ch.qos.logback 88 | logback-core 89 | ${logback.version} 90 | provided 91 | 92 | 93 | 94 | 95 | org.slf4j 96 | slf4j-api 97 | ${slf4j.version} 98 | provided 99 | 100 | 101 | 102 | 103 | org.apache.logging.log4j 104 | log4j-api 105 | ${log4j2.version} 106 | provided 107 | 108 | 109 | org.apache.logging.log4j 110 | log4j-core 111 | ${log4j2.version} 112 | provided 113 | 114 | 115 | 116 | 117 | com.aliyun.openservices 118 | aliyun-log-producer 119 | ${aliyun-log-producer.version} 120 | 121 | 122 | com.aliyun.openservices 123 | aliyun-log 124 | ${aliyun-log.version} 125 | 126 | 127 | 128 | 129 | org.apache.skywalking 130 | apm-toolkit-logback-1.x 131 | ${skywalking.toolkit.version} 132 | 133 | 134 | org.apache.skywalking 135 | apm-toolkit-log4j-2.x 136 | ${skywalking.toolkit.version} 137 | 138 | 139 | 140 | 141 | commons-beanutils 142 | commons-beanutils 143 | ${commons-beanutils.version} 144 | 145 | 146 | com.google.protobuf 147 | protobuf-java 148 | ${protobuf-java.version} 149 | 150 | 151 | joda-time 152 | joda-time 153 | ${joda-time.version} 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | repo 162 | http://ops.keking.cn:8081/nexus/content/repositories/snapshots 163 | 164 | 165 | repo 166 | http://ops.keking.cn:8081/nexus/content/repositories/releases 167 | 168 | 169 | 170 | 171 | -------------------------------------------------------------------------------- /src/main/java/cn/keking/common/log/Constants.java: -------------------------------------------------------------------------------- 1 | package cn.keking.common.log; 2 | 3 | /** 4 | * 5 | */ 6 | public interface Constants { 7 | 8 | String LOG_CONFIG_PREFIX = "aliyun.log"; 9 | 10 | String LOG_PROJECT_CONFIG_PREFIX = LOG_CONFIG_PREFIX + ".project"; 11 | 12 | String LOG_PRODUCER_CONFIG_PREFIX = LOG_CONFIG_PREFIX + ".producer"; 13 | 14 | String LOG_CONFIG_LOGGER_FILTER = "logger.filter"; 15 | 16 | String DEFAULT_ROOT_LOGGER_NAME = "ROOT"; 17 | } 18 | -------------------------------------------------------------------------------- /src/main/java/cn/keking/common/log/LogAppenderAutoConfiguration.java: -------------------------------------------------------------------------------- 1 | package cn.keking.common.log; 2 | import cn.keking.common.log.config.CommonLogAppenderConfigLoader; 3 | import cn.keking.common.log.config.LogAppenderConfig; 4 | import cn.keking.common.log.config.LogAppenderConfigLoader; 5 | import cn.keking.common.log.enhancer.LogEnhancer; 6 | import org.slf4j.Logger; 7 | import org.slf4j.LoggerFactory; 8 | import org.springframework.context.EnvironmentAware; 9 | import org.springframework.context.annotation.Configuration; 10 | import org.springframework.core.env.ConfigurableEnvironment; 11 | import org.springframework.core.env.Environment; 12 | 13 | 14 | /** 15 | * @Author wanglaomo 16 | * @Date 2019/4/9 17 | **/ 18 | @Configuration 19 | public class LogAppenderAutoConfiguration implements EnvironmentAware { 20 | 21 | private static final Logger logger = LoggerFactory.getLogger(LogAppenderAutoConfiguration.class); 22 | @Override 23 | public void setEnvironment(Environment environment) { 24 | 25 | logger.info("kk aliyun log appender has been integrated into system"); 26 | 27 | ConfigurableEnvironment env = (ConfigurableEnvironment)environment; 28 | 29 | // load config 30 | LogAppenderConfigLoader loader = new CommonLogAppenderConfigLoader(); 31 | final LogAppenderConfig config; 32 | try { 33 | config = loader.load(env); 34 | } catch (Exception e) { 35 | throw new IllegalStateException("Failed to load log appender config", e); 36 | } 37 | 38 | if(!config.isEnable()) { 39 | return; 40 | } 41 | 42 | // init logEnhancer 43 | ClassLoader classLoader = env.getClass().getClassLoader(); 44 | LogEnhancerBinder.bindClassLoader(classLoader); 45 | LogEnhancer logEnhancer = LogEnhancerBinder.getInstance(); 46 | 47 | // do nothing if aliyun appender has been bound 48 | if(!logEnhancer.alreadyBound()) { 49 | 50 | logEnhancer.enhance(config); 51 | logger.info("kk aliyun log appender has been successfully initialized"); 52 | } 53 | } 54 | 55 | 56 | } 57 | -------------------------------------------------------------------------------- /src/main/java/cn/keking/common/log/LogEnhancerBinder.java: -------------------------------------------------------------------------------- 1 | package cn.keking.common.log; 2 | 3 | import cn.keking.common.log.enhancer.DefaultLog4j2Enhancer; 4 | import cn.keking.common.log.enhancer.DefaultLogbackEnhancer; 5 | import cn.keking.common.log.enhancer.LogEnhancer; 6 | import cn.keking.common.log.utils.LogEnvUtils; 7 | 8 | /** 9 | * @Author wanglaomo 10 | * @Date 2019/4/3 11 | **/ 12 | public class LogEnhancerBinder { 13 | 14 | private volatile static LogEnhancer INSTANCE; 15 | 16 | private static ClassLoader classLoader = ClassLoader.getSystemClassLoader(); 17 | 18 | 19 | public static void bindClassLoader(ClassLoader classLoader) { 20 | 21 | if(INSTANCE == null) { 22 | 23 | synchronized (LogEnhancerBinder.class) { 24 | 25 | if(INSTANCE == null) { 26 | LogEnhancerBinder.classLoader = classLoader; 27 | } 28 | } 29 | } 30 | } 31 | 32 | public static LogEnhancer getInstance() { 33 | 34 | if(INSTANCE == null) { 35 | 36 | synchronized (LogEnhancerBinder.class) { 37 | 38 | if(INSTANCE == null) { 39 | 40 | INSTANCE = doInit(classLoader); 41 | } 42 | 43 | } 44 | } 45 | 46 | return INSTANCE; 47 | } 48 | 49 | private static LogEnhancer doInit(ClassLoader classLoader) { 50 | 51 | LogEnhancer enhancer = null; 52 | if(LogEnvUtils.isLog4j2Usable(classLoader)) { 53 | enhancer = new DefaultLog4j2Enhancer(classLoader); 54 | } else if(LogEnvUtils.isLogbackUsable(classLoader)) { 55 | enhancer = new DefaultLogbackEnhancer(classLoader); 56 | } else { 57 | throw new IllegalStateException("No applicable logging system found"); 58 | } 59 | 60 | return enhancer; 61 | } 62 | 63 | } 64 | -------------------------------------------------------------------------------- /src/main/java/cn/keking/common/log/appender/AliYunAppenderCallback.java: -------------------------------------------------------------------------------- 1 | package cn.keking.common.log.appender; 2 | 3 | import cn.keking.common.log.config.LogAppenderConfig; 4 | import com.aliyun.openservices.aliyun.log.producer.Callback; 5 | import com.aliyun.openservices.aliyun.log.producer.Result; 6 | import com.aliyun.openservices.log.common.LogItem; 7 | import org.slf4j.Logger; 8 | import org.slf4j.LoggerFactory; 9 | 10 | import java.util.List; 11 | 12 | 13 | /** 14 | * Common Aliyun appender producer callback 15 | * 16 | * @Author wanglaomo 17 | * @Date 2019/4/3 18 | **/ 19 | public class AliYunAppenderCallback implements Callback { 20 | 21 | private final Logger logger = LoggerFactory.getLogger(AliYunAppenderCallback.class); 22 | 23 | private final String project; 24 | 25 | private final String logStore; 26 | 27 | private final String topic; 28 | 29 | private final String source; 30 | 31 | private final List logItems; 32 | 33 | public AliYunAppenderCallback(LogAppenderConfig config, List logItems) { 34 | super(); 35 | this.project = config.getProjectConfig().getName(); 36 | this.logStore = config.getProjectConfig().getLogstore(); 37 | this.topic = config.getTopic(); 38 | this.source = config.getSource(); 39 | this.logItems = logItems; 40 | } 41 | 42 | @Override 43 | public void onCompletion(Result result) { 44 | 45 | if (!result.isSuccessful()) { 46 | logger.error("Failed to putLogs. project=" + project + " logStore=" + logStore + 47 | " topic=" + topic + " source=" + source + " logItems=" + logItems, result); 48 | } 49 | 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/main/java/cn/keking/common/log/appender/log4j2/Log4j2AliYunAppender.java: -------------------------------------------------------------------------------- 1 | package cn.keking.common.log.appender.log4j2; 2 | 3 | import cn.keking.common.log.appender.AliYunAppenderCallback; 4 | import cn.keking.common.log.config.LogAppenderConfig; 5 | import com.aliyun.openservices.aliyun.log.producer.*; 6 | import com.aliyun.openservices.aliyun.log.producer.errors.LogSizeTooLargeException; 7 | import com.aliyun.openservices.aliyun.log.producer.errors.TimeoutException; 8 | import com.aliyun.openservices.log.common.LogItem; 9 | import org.apache.logging.log4j.core.LogEvent; 10 | import org.apache.logging.log4j.core.appender.AbstractAppender; 11 | import org.apache.logging.log4j.core.layout.PatternLayout; 12 | import org.apache.logging.log4j.core.util.Throwables; 13 | import org.joda.time.DateTime; 14 | import org.joda.time.DateTimeZone; 15 | import org.joda.time.format.DateTimeFormat; 16 | import org.joda.time.format.DateTimeFormatter; 17 | 18 | import java.util.ArrayList; 19 | import java.util.Arrays; 20 | import java.util.List; 21 | import java.util.Map; 22 | 23 | 24 | /** 25 | * Aliyun appender for log4j2 26 | * 27 | * @Author wanglaomo 28 | * @Date 2019/4/3 29 | **/ 30 | public class Log4j2AliYunAppender extends AbstractAppender { 31 | 32 | public static final String APPENDER_NAME = "ALI_YUN_APPENDER"; 33 | 34 | private Producer producer; 35 | 36 | private final LogAppenderConfig config; 37 | 38 | protected DateTimeFormatter formatter; 39 | 40 | public Log4j2AliYunAppender(LogAppenderConfig config) { 41 | 42 | super(APPENDER_NAME, null, null); 43 | this.config = config; 44 | } 45 | 46 | 47 | public Log4j2AliYunAppender(LogAppenderConfig config, PatternLayout layout) { 48 | 49 | super(APPENDER_NAME, null, layout); 50 | this.config = config; 51 | } 52 | 53 | @Override 54 | public void start() { 55 | super.start(); 56 | 57 | formatter = DateTimeFormat.forPattern(config.getTimeFormat()).withZone(DateTimeZone.forID(config.getTimeZone())); 58 | ProducerConfig producerConfig = config.getProducerConfig(); 59 | if (producerConfig == null) { 60 | ProjectConfigs projectConfigs = new ProjectConfigs(); 61 | projectConfigs.put(config.getProjectConfig().buildProjectConfig()); 62 | producerConfig = new ProducerConfig(projectConfigs); 63 | } 64 | producer = new LogProducer(producerConfig); 65 | } 66 | 67 | @Override 68 | public void stop() { 69 | super.stop(); 70 | if (producer != null) { 71 | try { 72 | producer.close(); 73 | } catch (Exception e) { 74 | LOGGER.error("Failed to close aliyun log producer: ", e); 75 | } 76 | } 77 | } 78 | 79 | @Override 80 | public void append(LogEvent event) { 81 | 82 | List logItems = new ArrayList(); 83 | LogItem item = new LogItem(); 84 | logItems.add(item); 85 | item.SetTime((int) (event.getTimeMillis() / 1000)); 86 | DateTime dateTime = new DateTime(event.getTimeMillis()); 87 | item.PushBack("time", dateTime.toString(formatter)); 88 | item.PushBack("level", event.getLevel().toString()); 89 | item.PushBack("thread", event.getThreadName()); 90 | 91 | StackTraceElement source = event.getSource(); 92 | if (source == null && (!event.isIncludeLocation())) { 93 | event.setIncludeLocation(true); 94 | source = event.getSource(); 95 | event.setIncludeLocation(false); 96 | } 97 | 98 | item.PushBack("location", source == null ? "Unknown(Unknown Source)" : source.toString()); 99 | 100 | String message = event.getMessage().getFormattedMessage(); 101 | item.PushBack("message", message); 102 | 103 | String throwable = getThrowableStr(event.getThrown()); 104 | if (throwable != null) { 105 | item.PushBack("throwable", throwable); 106 | } 107 | 108 | if (getLayout() != null) { 109 | item.PushBack("log", new String(getLayout().toByteArray(event))); 110 | } 111 | 112 | Map properties = event.getContextMap(); 113 | if (properties.size() > 0) { 114 | Object[] keys = properties.keySet().toArray(); 115 | Arrays.sort(keys); 116 | for (int i = 0; i < keys.length; i++) { 117 | item.PushBack(keys[i].toString(), properties.get(keys[i].toString())); 118 | } 119 | } 120 | 121 | try { 122 | producer.send( 123 | config.getProjectConfig().getName(), 124 | config.getProjectConfig().getLogstore(), 125 | config.getTopic(), 126 | null, 127 | logItems, 128 | new AliYunAppenderCallback(config, logItems)); 129 | } catch (InterruptedException e) { 130 | LOGGER.warn("The current thread has been interrupted during send logs."); 131 | } catch (Exception e) { 132 | if (e instanceof LogSizeTooLargeException) { 133 | LOGGER.error("The size of log is larger than the maximum allowable size, e={}", e); 134 | } else if (e instanceof TimeoutException) { 135 | LOGGER.error("The time taken for allocating memory for the logs has surpassed., e={}", e); 136 | } else { 137 | LOGGER.error("Failed to send log, logItems={}, e=", logItems, e); 138 | } 139 | } 140 | } 141 | 142 | private String getThrowableStr(Throwable throwable) { 143 | if (throwable == null) { 144 | return null; 145 | } 146 | StringBuilder sb = new StringBuilder(); 147 | boolean isFirst = true; 148 | for (String s : Throwables.toStringList(throwable)) { 149 | if (isFirst) { 150 | isFirst = false; 151 | } else { 152 | sb.append(System.getProperty("line.separator")); 153 | } 154 | sb.append(s); 155 | } 156 | return sb.toString(); 157 | } 158 | 159 | public LogAppenderConfig getConfig() { 160 | return config; 161 | } 162 | 163 | 164 | } 165 | -------------------------------------------------------------------------------- /src/main/java/cn/keking/common/log/appender/logback/LogbackAliYunAppender.java: -------------------------------------------------------------------------------- 1 | package cn.keking.common.log.appender.logback; 2 | 3 | import ch.qos.logback.classic.spi.IThrowableProxy; 4 | import ch.qos.logback.classic.spi.LoggingEvent; 5 | import ch.qos.logback.classic.spi.StackTraceElementProxy; 6 | import ch.qos.logback.classic.spi.ThrowableProxyUtil; 7 | import ch.qos.logback.core.CoreConstants; 8 | import ch.qos.logback.core.UnsynchronizedAppenderBase; 9 | import ch.qos.logback.core.encoder.Encoder; 10 | import cn.keking.common.log.appender.AliYunAppenderCallback; 11 | import cn.keking.common.log.config.LogAppenderConfig; 12 | import com.aliyun.openservices.aliyun.log.producer.LogProducer; 13 | import com.aliyun.openservices.aliyun.log.producer.ProducerConfig; 14 | import com.aliyun.openservices.aliyun.log.producer.ProjectConfigs; 15 | import com.aliyun.openservices.aliyun.log.producer.errors.LogSizeTooLargeException; 16 | import com.aliyun.openservices.aliyun.log.producer.errors.ProducerException; 17 | import com.aliyun.openservices.aliyun.log.producer.errors.TimeoutException; 18 | import com.aliyun.openservices.log.common.LogItem; 19 | import org.joda.time.DateTime; 20 | import org.joda.time.DateTimeZone; 21 | import org.joda.time.format.DateTimeFormat; 22 | import org.joda.time.format.DateTimeFormatter; 23 | 24 | import java.util.ArrayList; 25 | import java.util.List; 26 | 27 | /** 28 | * @ClassName LogbackAliYunAppender 29 | * @Description 30 | * @Author wanglaomo 31 | * @Date 2019/4/2 32 | **/ 33 | public class LogbackAliYunAppender extends UnsynchronizedAppenderBase { 34 | 35 | private Encoder encoder; 36 | 37 | protected LogAppenderConfig config; 38 | 39 | private LogProducer producer; 40 | 41 | private DateTimeFormatter formatter; 42 | 43 | public LogbackAliYunAppender() { 44 | } 45 | 46 | public LogbackAliYunAppender(LogAppenderConfig logAppenderConfig) { 47 | 48 | this.config = logAppenderConfig; 49 | } 50 | 51 | @Override 52 | public void start() { 53 | try { 54 | doStart(); 55 | } catch (Exception e) { 56 | addError("Failed to start LogbackAliYunAppender.", e); 57 | } 58 | } 59 | 60 | private void doStart() { 61 | 62 | formatter = DateTimeFormat.forPattern(config.getTimeFormat()).withZone(DateTimeZone.forID(config.getTimeZone())); 63 | if(config.getProducerConfig() == null){ 64 | ProjectConfigs projectConfigs = new ProjectConfigs(); 65 | projectConfigs.put(config.getProjectConfig().buildProjectConfig()); 66 | ProducerConfig producerConfig = new ProducerConfig(projectConfigs); 67 | config.setProducerConfig(producerConfig); 68 | } 69 | producer = new LogProducer(config.getProducerConfig()); 70 | 71 | super.start(); 72 | } 73 | 74 | @Override 75 | public void stop() { 76 | try { 77 | doStop(); 78 | } catch (Exception e) { 79 | addError("Failed to stop LogbackAliYunAppender.", e); 80 | } 81 | } 82 | 83 | private void doStop() throws InterruptedException, ProducerException { 84 | if (!isStarted()){ 85 | return; 86 | } 87 | super.stop(); 88 | if (producer != null) { 89 | producer.close(); 90 | } 91 | } 92 | 93 | @Override 94 | public void append(E eventObject) { 95 | 96 | //init Event Object 97 | if (!(eventObject instanceof LoggingEvent)) { 98 | return; 99 | } 100 | LoggingEvent event = (LoggingEvent) eventObject; 101 | 102 | List logItems = new ArrayList(); 103 | LogItem item = new LogItem(); 104 | 105 | 106 | logItems.add(item); 107 | item.SetTime((int) (event.getTimeStamp() / 1000)); 108 | 109 | DateTime dateTime = new DateTime(event.getTimeStamp()); 110 | item.PushBack("time", dateTime.toString(formatter)); 111 | item.PushBack("level", event.getLevel().toString()); 112 | item.PushBack("thread", event.getThreadName()); 113 | 114 | StackTraceElement[] caller = event.getCallerData(); 115 | if (caller != null && caller.length > 0) { 116 | item.PushBack("location", caller[0].toString()); 117 | } 118 | 119 | String message = event.getFormattedMessage(); 120 | item.PushBack("message", message); 121 | 122 | IThrowableProxy iThrowableProxy = event.getThrowableProxy(); 123 | if (iThrowableProxy != null) { 124 | String throwable = getExceptionInfo(iThrowableProxy); 125 | throwable += fullDump(event.getThrowableProxy().getStackTraceElementProxyArray()); 126 | item.PushBack("throwable", throwable); 127 | } 128 | 129 | if (this.encoder != null) { 130 | item.PushBack("log", new String(this.encoder.encode(eventObject))); 131 | } 132 | 133 | try { 134 | producer.send( 135 | config.getProjectConfig().getName(), 136 | config.getProjectConfig().getLogstore(), 137 | config.getTopic(), 138 | null, 139 | logItems, 140 | new AliYunAppenderCallback(config, logItems)); 141 | } catch (InterruptedException e) { 142 | addError("The current thread has been interrupted during send logs."); 143 | } catch (Exception e) { 144 | if (e instanceof LogSizeTooLargeException) { 145 | addError("The size of log is larger than the maximum allowable size, e={}", e); 146 | } else if (e instanceof TimeoutException) { 147 | addError("The time taken for allocating memory for the logs has surpassed., e={}", e); 148 | } else { 149 | addError("Failed to send log, e=", e); 150 | } 151 | } 152 | } 153 | 154 | private String getExceptionInfo(IThrowableProxy iThrowableProxy) { 155 | String s = iThrowableProxy.getClassName(); 156 | String message = iThrowableProxy.getMessage(); 157 | return (message != null) ? (s + ": " + message) : s; 158 | } 159 | 160 | private String fullDump(StackTraceElementProxy[] stackTraceElementProxyArray) { 161 | StringBuilder builder = new StringBuilder(); 162 | for (StackTraceElementProxy step : stackTraceElementProxyArray) { 163 | builder.append(CoreConstants.LINE_SEPARATOR); 164 | String string = step.toString(); 165 | builder.append(CoreConstants.TAB).append(string); 166 | ThrowableProxyUtil.subjoinPackagingData(builder, step); 167 | } 168 | return builder.toString(); 169 | } 170 | 171 | public LogAppenderConfig getConfig() { 172 | return config; 173 | } 174 | 175 | public void setConfig(LogAppenderConfig config) { 176 | this.config = config; 177 | } 178 | 179 | public void setEncoder(Encoder encoder) { 180 | this.encoder = encoder; 181 | } 182 | } 183 | -------------------------------------------------------------------------------- /src/main/java/cn/keking/common/log/config/CommonLogAppenderConfigLoader.java: -------------------------------------------------------------------------------- 1 | package cn.keking.common.log.config; 2 | 3 | import cn.keking.common.log.Constants; 4 | import com.aliyun.openservices.aliyun.log.producer.ProducerConfig; 5 | import com.aliyun.openservices.aliyun.log.producer.ProjectConfigs; 6 | import org.apache.commons.beanutils.BeanUtils; 7 | import org.apache.commons.lang.StringUtils; 8 | import org.springframework.core.env.ConfigurableEnvironment; 9 | import org.springframework.core.env.EnumerablePropertySource; 10 | import org.springframework.core.env.PropertySource; 11 | 12 | import java.lang.reflect.InvocationTargetException; 13 | import java.util.*; 14 | 15 | /** 16 | * Load log appender config from environment 17 | * 18 | * @Author wanglaomo 19 | * @Date 2019/4/8 20 | **/ 21 | public class CommonLogAppenderConfigLoader implements LogAppenderConfigLoader { 22 | 23 | @Override 24 | public LogAppenderConfig load(ConfigurableEnvironment environment) throws InvocationTargetException, IllegalAccessException { 25 | 26 | LogAppenderConfig config = readConfigFromContext(environment); 27 | 28 | return config; 29 | } 30 | 31 | private LogAppenderConfig readConfigFromContext(ConfigurableEnvironment environment) throws InvocationTargetException, IllegalAccessException { 32 | 33 | Set configKeys = new HashSet<>(); 34 | Iterator> propertySourceIterator = environment.getPropertySources().iterator(); 35 | while (propertySourceIterator.hasNext()) { 36 | PropertySource propertySource = propertySourceIterator.next(); 37 | if (propertySource instanceof EnumerablePropertySource) { 38 | configKeys.addAll(Arrays.asList(((EnumerablePropertySource) propertySource) 39 | .getPropertyNames())); 40 | } 41 | } 42 | 43 | Map context = new HashMap<>(); 44 | Map projectContext = new HashMap<>(); 45 | Map producerContext = new HashMap<>(); 46 | for(String key : configKeys) { 47 | if(filterAllLogConfig(key)) { 48 | if(key.startsWith(Constants.LOG_PRODUCER_CONFIG_PREFIX)) { 49 | producerContext.put( 50 | parseConfigKey(Constants.LOG_PRODUCER_CONFIG_PREFIX, key), 51 | environment.getProperty(key)); 52 | } else if(key.startsWith(Constants.LOG_PROJECT_CONFIG_PREFIX)) { 53 | projectContext.put( 54 | parseConfigKey(Constants.LOG_PROJECT_CONFIG_PREFIX, key), 55 | environment.getProperty(key)); 56 | } else { 57 | context.put( 58 | parseConfigKey(Constants.LOG_CONFIG_PREFIX, key), 59 | environment.getProperty(key)); 60 | } 61 | } 62 | } 63 | 64 | LogAppenderConfig config = new LogAppenderConfig(); 65 | BeanUtils.populate(config, context); 66 | String filterStr = context.get(Constants.LOG_CONFIG_LOGGER_FILTER); 67 | if(!StringUtils.isBlank(filterStr)) { 68 | List loggerFilter = Arrays.asList(filterStr.split(",")); 69 | config.getLoggerFilter().addAll(loggerFilter); 70 | } 71 | if(StringUtils.isBlank(config.getTopic())) { 72 | throw new IllegalStateException("Topic must not be null"); 73 | } 74 | 75 | CommonProjectConfig commonProjectConfig = CommonProjectConfig.instance(); 76 | BeanUtils.populate(commonProjectConfig, projectContext); 77 | 78 | ProjectConfigs projectConfigs = new ProjectConfigs(); 79 | projectConfigs.put(commonProjectConfig.buildProjectConfig()); 80 | ProducerConfig producerConfig = new ProducerConfig(projectConfigs); 81 | BeanUtils.populate(producerConfig, producerContext); 82 | 83 | config.setProducerConfig(producerConfig); 84 | config.setProjectConfig(commonProjectConfig); 85 | 86 | return config; 87 | } 88 | 89 | private String parseConfigKey(String prefix, String key) { 90 | return key.replace(prefix+".", ""); 91 | } 92 | 93 | private boolean filterAllLogConfig(String key) { 94 | 95 | return key.startsWith(Constants.LOG_CONFIG_PREFIX); 96 | } 97 | 98 | } 99 | -------------------------------------------------------------------------------- /src/main/java/cn/keking/common/log/config/CommonProjectConfig.java: -------------------------------------------------------------------------------- 1 | package cn.keking.common.log.config; 2 | 3 | import com.aliyun.openservices.aliyun.log.producer.ProjectConfig; 4 | import org.springframework.core.io.support.PropertiesLoaderUtils; 5 | 6 | import java.io.IOException; 7 | import java.util.Properties; 8 | 9 | /** 10 | * @Author wanglaomo 11 | * @Date 2019/4/8 12 | **/ 13 | public class CommonProjectConfig { 14 | 15 | private static final String CONFIG_FILENAME = "aliyunlog.properties"; 16 | 17 | private static final String DEFAULT_LOGSTORE = "logstore"; 18 | 19 | private static final String DEFAULT_PROJECT_NAME = "name"; 20 | 21 | private static final String DEFAULT_ENDPOINT = "endpoint"; 22 | 23 | private static final String DEFAULT_ACCESSKEY_ID = "accessKeyId"; 24 | 25 | private static final String DEFAULT_ACCESSKEY_SECRET = "accessKeySecret"; 26 | 27 | private static final String DEFAULT_STS_TOKEN = "stsToken"; 28 | 29 | private static final String DEFAULT_USER_AGENT = "userAgent"; 30 | 31 | private String name; 32 | 33 | private String logstore; 34 | 35 | private String endpoint; 36 | 37 | private String accessKeyId; 38 | 39 | private String accessKeySecret; 40 | 41 | private String stsToken; 42 | 43 | private String userAgent; 44 | 45 | private final static CommonProjectConfig COMMON_PROJECT_CONFIG = new CommonProjectConfig(); 46 | 47 | private CommonProjectConfig() { 48 | try { 49 | Properties properties = PropertiesLoaderUtils.loadAllProperties(CONFIG_FILENAME, CommonProjectConfig.class.getClassLoader()); 50 | this.name = properties.getProperty(DEFAULT_PROJECT_NAME); 51 | this.logstore = properties.getProperty(DEFAULT_LOGSTORE); 52 | this.endpoint = properties.getProperty(DEFAULT_ENDPOINT); 53 | this.accessKeyId = properties.getProperty(DEFAULT_ACCESSKEY_ID); 54 | this.accessKeySecret = properties.getProperty(DEFAULT_ACCESSKEY_SECRET); 55 | this.stsToken = properties.getProperty(DEFAULT_STS_TOKEN); 56 | this.userAgent = properties.getProperty(DEFAULT_USER_AGENT); 57 | }catch (IOException ex){ 58 | ex.printStackTrace(); 59 | } 60 | } 61 | 62 | public static CommonProjectConfig instance(){ 63 | return COMMON_PROJECT_CONFIG; 64 | } 65 | 66 | public ProjectConfig buildProjectConfig() { 67 | 68 | return new ProjectConfig(name, endpoint, accessKeyId, accessKeySecret, stsToken, userAgent); 69 | } 70 | 71 | public String getLogstore() { 72 | return logstore; 73 | } 74 | 75 | public void setLogstore(String logstore) { 76 | this.logstore = logstore; 77 | } 78 | 79 | public String getName() { 80 | return name; 81 | } 82 | 83 | public void setName(String name) { 84 | this.name = name; 85 | } 86 | 87 | public String getEndpoint() { 88 | return endpoint; 89 | } 90 | 91 | public void setEndpoint(String endpoint) { 92 | this.endpoint = endpoint; 93 | } 94 | 95 | public String getAccessKeyId() { 96 | return accessKeyId; 97 | } 98 | 99 | public void setAccessKeyId(String accessKeyId) { 100 | this.accessKeyId = accessKeyId; 101 | } 102 | 103 | public String getAccessKeySecret() { 104 | return accessKeySecret; 105 | } 106 | 107 | public void setAccessKeySecret(String accessKeySecret) { 108 | this.accessKeySecret = accessKeySecret; 109 | } 110 | 111 | public String getStsToken() { 112 | return stsToken; 113 | } 114 | 115 | public void setStsToken(String stsToken) { 116 | this.stsToken = stsToken; 117 | } 118 | 119 | public String getUserAgent() { 120 | return userAgent; 121 | } 122 | 123 | public void setUserAgent(String userAgent) { 124 | this.userAgent = userAgent; 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /src/main/java/cn/keking/common/log/config/LogAppenderConfig.java: -------------------------------------------------------------------------------- 1 | package cn.keking.common.log.config; 2 | 3 | import com.aliyun.openservices.aliyun.log.producer.ProducerConfig; 4 | 5 | import java.util.ArrayList; 6 | import java.util.List; 7 | 8 | 9 | /** 10 | * 通用阿里云日志服务appender配置 11 | * 12 | * @Author wanglaomo 13 | * @Date 2019/4/4 14 | **/ 15 | public class LogAppenderConfig { 16 | 17 | private static final String DEFAULT_PATTERN = "%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n"; 18 | 19 | private CommonProjectConfig projectConfig; 20 | 21 | private ProducerConfig producerConfig; 22 | 23 | private boolean enable = false; 24 | 25 | private String topic; // 26 | 27 | private String source; // 28 | 29 | private String timeZone = "UTC"; 30 | 31 | private String charset = "UTF-8"; 32 | 33 | private String timeFormat = "yyyy-MM-dd'T'HH:mmZ"; 34 | 35 | private List loggerFilter = new ArrayList<>(); 36 | 37 | private String pattern = DEFAULT_PATTERN; 38 | 39 | public boolean isEnable() { 40 | return enable; 41 | } 42 | 43 | public void setEnable(boolean enable) { 44 | this.enable = enable; 45 | } 46 | 47 | public String getTopic() { 48 | return topic; 49 | } 50 | 51 | public void setTopic(String topic) { 52 | this.topic = topic; 53 | } 54 | 55 | public String getTimeZone() { 56 | return timeZone; 57 | } 58 | 59 | public void setTimeZone(String timeZone) { 60 | this.timeZone = timeZone; 61 | } 62 | 63 | public String getTimeFormat() { 64 | return timeFormat; 65 | } 66 | 67 | public void setTimeFormat(String timeFormat) { 68 | this.timeFormat = timeFormat; 69 | } 70 | 71 | public CommonProjectConfig getProjectConfig() { 72 | return projectConfig; 73 | } 74 | 75 | public void setProjectConfig(CommonProjectConfig projectConfig) { 76 | this.projectConfig = projectConfig; 77 | } 78 | 79 | public ProducerConfig getProducerConfig() { 80 | return producerConfig; 81 | } 82 | 83 | public void setProducerConfig(ProducerConfig producerConfig) { 84 | this.producerConfig = producerConfig; 85 | } 86 | 87 | public List getLoggerFilter() { 88 | return loggerFilter; 89 | } 90 | 91 | public void setLoggerFilter(List loggerFilter) { 92 | this.loggerFilter = loggerFilter; 93 | } 94 | 95 | public String getPattern() { 96 | return pattern; 97 | } 98 | 99 | public void setPattern(String pattern) { 100 | this.pattern = pattern; 101 | } 102 | 103 | public String getSource() { 104 | return source; 105 | } 106 | 107 | public String getCharset() { 108 | return charset; 109 | } 110 | 111 | public void setCharset(String charset) { 112 | this.charset = charset; 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /src/main/java/cn/keking/common/log/config/LogAppenderConfigLoader.java: -------------------------------------------------------------------------------- 1 | package cn.keking.common.log.config; 2 | 3 | import org.springframework.core.env.ConfigurableEnvironment; 4 | 5 | import java.lang.reflect.InvocationTargetException; 6 | 7 | /** 8 | * Config loader 9 | * 10 | * @Author wanglaomo 11 | * @Date 2019/4/8 12 | **/ 13 | public interface LogAppenderConfigLoader { 14 | 15 | // 从spring environment获取配置 16 | LogAppenderConfig load(ConfigurableEnvironment environment) throws InvocationTargetException, IllegalAccessException; 17 | 18 | } 19 | -------------------------------------------------------------------------------- /src/main/java/cn/keking/common/log/enhancer/AbstractLogEnhancer.java: -------------------------------------------------------------------------------- 1 | package cn.keking.common.log.enhancer; 2 | 3 | import cn.keking.common.log.config.LogAppenderConfig; 4 | 5 | /** 6 | * Abstract log enhancer 7 | * 8 | * @Author wanglaomo 9 | * @Date 2019/4/4 10 | **/ 11 | public abstract class AbstractLogEnhancer implements LogEnhancer { 12 | 13 | @Override 14 | public void enhance(LogAppenderConfig config) { 15 | 16 | try { 17 | if(!hasBeanEnhanced()) { 18 | doEnhance(config); 19 | } 20 | } catch (Exception e) { 21 | handlerEnhanceError(e); 22 | } finally { 23 | afterEnhance(); 24 | } 25 | } 26 | 27 | protected abstract boolean hasBeanEnhanced(); 28 | 29 | protected abstract void doEnhance(LogAppenderConfig config); 30 | 31 | protected abstract void afterEnhance(); 32 | 33 | protected void handlerEnhanceError(Exception exception) { 34 | 35 | cleanUp(); 36 | System.err.println("Failed to add aliyun appender"); 37 | exception.printStackTrace(System.err); 38 | throw new IllegalStateException(exception); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/main/java/cn/keking/common/log/enhancer/DefaultLog4j2Enhancer.java: -------------------------------------------------------------------------------- 1 | package cn.keking.common.log.enhancer; 2 | 3 | import cn.keking.common.log.Constants; 4 | import cn.keking.common.log.appender.log4j2.Log4j2AliYunAppender; 5 | import cn.keking.common.log.config.LogAppenderConfig; 6 | import cn.keking.common.log.utils.LogEnvUtils; 7 | import org.apache.commons.lang.StringUtils; 8 | import org.apache.logging.log4j.LogManager; 9 | import org.apache.logging.log4j.core.Appender; 10 | import org.apache.logging.log4j.core.LoggerContext; 11 | import org.apache.logging.log4j.core.config.Configuration; 12 | import org.apache.logging.log4j.core.config.LoggerConfig; 13 | import org.apache.logging.log4j.core.layout.PatternLayout; 14 | import org.springframework.util.ClassUtils; 15 | 16 | import java.nio.charset.Charset; 17 | import java.util.List; 18 | import java.util.Map; 19 | 20 | 21 | /** 22 | * Default log enhancer for log4j2 23 | * 24 | * @Author wanglaomo 25 | * @Date 2019/4/3 26 | **/ 27 | public class DefaultLog4j2Enhancer extends AbstractLogEnhancer{ 28 | 29 | private final ClassLoader classLoader; 30 | 31 | private LoggerContext ctx; 32 | 33 | public DefaultLog4j2Enhancer(ClassLoader classLoader) { 34 | 35 | this.classLoader = classLoader; 36 | ctx = getLoggerContext(); 37 | } 38 | 39 | @Override 40 | public boolean alreadyBound() { 41 | 42 | Class clazz = LogEnvUtils.loadAliyunLog4j2AppenderExist(classLoader); 43 | if(clazz == null) { 44 | return false; 45 | } else { 46 | 47 | Map appenderMap = ctx.getConfiguration().getAppenders(); 48 | for (Map.Entry entry : appenderMap.entrySet()) { 49 | String name = entry.getKey(); 50 | Appender appender = entry.getValue(); 51 | if (appender.getClass().isAssignableFrom(clazz)) { 52 | return true; 53 | } 54 | } 55 | return false; 56 | } 57 | 58 | 59 | } 60 | 61 | @Override 62 | public void cleanUp() { 63 | 64 | markAsUnEnhanced(); 65 | } 66 | 67 | protected void doEnhance(LogAppenderConfig logAppenderConfig) { 68 | 69 | final Configuration config = ctx.getConfiguration(); 70 | 71 | // config pattern 72 | Appender appender = null; 73 | String pattern = logAppenderConfig.getPattern(); 74 | if(!StringUtils.isBlank(pattern)) { 75 | PatternLayout layout = PatternLayout 76 | .newBuilder() 77 | .withPattern(pattern) 78 | .withCharset(Charset.forName(logAppenderConfig.getCharset())) 79 | .build(); 80 | appender = new Log4j2AliYunAppender(logAppenderConfig, layout); 81 | } else { 82 | appender = new Log4j2AliYunAppender(logAppenderConfig); 83 | } 84 | 85 | appender.start(); 86 | config.addAppender(appender); 87 | List loggerFilter = logAppenderConfig.getLoggerFilter(); 88 | // log4j2 root logger name is "" 89 | if(loggerFilter.remove(Constants.DEFAULT_ROOT_LOGGER_NAME)) { 90 | loggerFilter.add(""); 91 | } 92 | 93 | final Map loggerMap = config.getLoggers(); 94 | Appender finalAppender = appender; 95 | loggerMap.forEach((loggerName, loggerConfig) -> { 96 | if(!loggerFilter.contains(loggerName)) { 97 | loggerConfig.addAppender(finalAppender, null, null); 98 | } 99 | }); 100 | 101 | ctx.updateLoggers(); 102 | } 103 | 104 | protected void afterEnhance() { 105 | markAsEnhanced(); 106 | } 107 | 108 | protected boolean hasBeanEnhanced() { 109 | 110 | if(ctx.getConfiguration().getProperties() 111 | .get(DefaultLog4j2Enhancer.class.getCanonicalName()) != null) { 112 | 113 | return true; 114 | } 115 | 116 | return false; 117 | } 118 | 119 | private void markAsEnhanced() { 120 | 121 | ctx.getConfiguration().getProperties() 122 | .put(DefaultLog4j2Enhancer.class.getCanonicalName(), ""); 123 | } 124 | 125 | private void markAsUnEnhanced() { 126 | 127 | ctx.getConfiguration().getProperties() 128 | .remove(DefaultLog4j2Enhancer.class.getCanonicalName()); 129 | } 130 | 131 | private LoggerContext getLoggerContext() { 132 | 133 | try { 134 | ClassUtils.forName(LogManager.class.getName(), classLoader); 135 | } catch (ClassNotFoundException e) { 136 | throw new IllegalStateException("Cannot find LogManager, but Log4j2 is on the classpath"); 137 | } 138 | 139 | return (LoggerContext) LogManager.getContext(false); 140 | } 141 | 142 | } 143 | -------------------------------------------------------------------------------- /src/main/java/cn/keking/common/log/enhancer/DefaultLogbackEnhancer.java: -------------------------------------------------------------------------------- 1 | package cn.keking.common.log.enhancer; 2 | 3 | import ch.qos.logback.classic.Level; 4 | import ch.qos.logback.classic.Logger; 5 | import ch.qos.logback.classic.LoggerContext; 6 | import ch.qos.logback.classic.spi.ILoggingEvent; 7 | import ch.qos.logback.classic.turbo.TurboFilter; 8 | import ch.qos.logback.core.Appender; 9 | import ch.qos.logback.core.spi.FilterReply; 10 | import cn.keking.common.log.appender.logback.LogbackAliYunAppender; 11 | import cn.keking.common.log.config.LogAppenderConfig; 12 | import cn.keking.common.log.utils.LogEnvUtils; 13 | import org.apache.commons.lang.StringUtils; 14 | import org.apache.skywalking.apm.toolkit.log.logback.v1.x.TraceIdPatternLogbackLayout; 15 | import org.slf4j.ILoggerFactory; 16 | import org.slf4j.Marker; 17 | import org.slf4j.impl.StaticLoggerBinder; 18 | 19 | import java.nio.charset.Charset; 20 | import java.util.Iterator; 21 | import java.util.List; 22 | 23 | /** 24 | * Default log enhancer for logback 25 | * 26 | * @Author wanglaomo 27 | * @Date 2019/4/3 28 | **/ 29 | public class DefaultLogbackEnhancer extends AbstractLogEnhancer { 30 | 31 | private final ClassLoader classLoader; 32 | 33 | private static LoggerContext ctx; 34 | 35 | public DefaultLogbackEnhancer(ClassLoader classLoader) { 36 | this.classLoader = classLoader; 37 | ctx = getLoggerContext(); 38 | } 39 | 40 | @Override 41 | public boolean alreadyBound() { 42 | 43 | Class clazz = LogEnvUtils.loadAliyunLogbackAppenderExist(classLoader); 44 | if(clazz == null) { 45 | return false; 46 | } else { 47 | List loggerList = ctx.getLoggerList(); 48 | for(Logger logger : loggerList) { 49 | Iterator> iterator = logger.iteratorForAppenders(); 50 | while(iterator.hasNext()) { 51 | Appender appender = iterator.next(); 52 | if (appender.getClass().isAssignableFrom(clazz)) { 53 | return true; 54 | } 55 | } 56 | } 57 | return false; 58 | } 59 | 60 | } 61 | 62 | @Override 63 | public void cleanUp() { 64 | 65 | markAsUnEnhanced(); 66 | } 67 | 68 | @Override 69 | protected void doEnhance(LogAppenderConfig config) { 70 | 71 | LogbackAliYunAppender aliYunAppender = new LogbackAliYunAppender(config); 72 | aliYunAppender.setContext(ctx); 73 | 74 | // config pattern 75 | if(!StringUtils.isBlank(config.getPattern())) { 76 | PatternLayoutEncoder encoder = new PatternLayoutEncoder(); 77 | encoder.setPattern(config.getPattern()); 78 | encoder.setContext(ctx); 79 | encoder.setPatternLayout(new TraceIdPatternLogbackLayout()); 80 | encoder.start(); 81 | encoder.setCharset(Charset.forName(config.getCharset())); 82 | aliYunAppender.setEncoder(encoder); 83 | } 84 | 85 | aliYunAppender.start(); 86 | List loggerFilter = config.getLoggerFilter(); 87 | 88 | ctx.addTurboFilter(new TurboFilter() { 89 | 90 | @Override 91 | public FilterReply decide(Marker marker, Logger logger, Level level, String format, Object[] params, Throwable t) { 92 | 93 | if (!logger.isAttached(aliYunAppender) && !loggerFilter.contains(logger.getName())) { 94 | 95 | logger.addAppender(aliYunAppender); 96 | } 97 | return FilterReply.NEUTRAL; 98 | } 99 | }); 100 | } 101 | 102 | 103 | protected void afterEnhance() { 104 | markAsEnhanced(); 105 | } 106 | 107 | protected boolean hasBeanEnhanced() { 108 | return ctx.getObject(DefaultLogbackEnhancer.class.getCanonicalName()) != null; 109 | } 110 | 111 | private void markAsEnhanced() { 112 | 113 | ctx.putObject(DefaultLogbackEnhancer.class.getCanonicalName(), new Object()); 114 | } 115 | 116 | private void markAsUnEnhanced() { 117 | 118 | ctx.removeObject(DefaultLogbackEnhancer.class.getCanonicalName()); 119 | } 120 | 121 | private LoggerContext getLoggerContext() { 122 | ILoggerFactory factory = StaticLoggerBinder.getSingleton().getLoggerFactory(); 123 | 124 | if(factory instanceof LoggerContext) { 125 | 126 | return (LoggerContext) factory; 127 | } 128 | throw new IllegalStateException("ILoggerFactory is not a Logback LoggerContext, but Logback is on the classpath."); 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /src/main/java/cn/keking/common/log/enhancer/LogEnhancer.java: -------------------------------------------------------------------------------- 1 | package cn.keking.common.log.enhancer; 2 | 3 | import cn.keking.common.log.config.LogAppenderConfig; 4 | 5 | /** 6 | * @Author wanglaomo 7 | * @Date 2019/4/3 8 | **/ 9 | public interface LogEnhancer { 10 | 11 | // 判断是否已经绑定阿里云SDK 12 | boolean alreadyBound(); 13 | 14 | // 接入阿里云日志服务 15 | void enhance(LogAppenderConfig config); 16 | 17 | void cleanUp(); 18 | } 19 | -------------------------------------------------------------------------------- /src/main/java/cn/keking/common/log/enhancer/PatternLayoutEncoder.java: -------------------------------------------------------------------------------- 1 | package cn.keking.common.log.enhancer; 2 | 3 | import ch.qos.logback.classic.PatternLayout; 4 | import ch.qos.logback.classic.spi.ILoggingEvent; 5 | import ch.qos.logback.core.pattern.PatternLayoutEncoderBase; 6 | 7 | /** 8 | * @Author wanglaomo 9 | * @Date 2019/4/12 10 | **/ 11 | public class PatternLayoutEncoder extends PatternLayoutEncoderBase { 12 | 13 | private PatternLayout patternLayout; 14 | 15 | @Override 16 | public void start() { 17 | patternLayout.setContext(context); 18 | patternLayout.setPattern(getPattern()); 19 | patternLayout.setOutputPatternAsHeader(outputPatternAsHeader); 20 | patternLayout.start(); 21 | this.layout = patternLayout; 22 | super.start(); 23 | } 24 | 25 | public void setPatternLayout(PatternLayout layout) { 26 | 27 | this.patternLayout = layout; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/main/java/cn/keking/common/log/utils/LogEnvUtils.java: -------------------------------------------------------------------------------- 1 | package cn.keking.common.log.utils; 2 | 3 | public final class LogEnvUtils { 4 | 5 | private LogEnvUtils() { 6 | } 7 | 8 | public static boolean isLogbackUsable(ClassLoader classloader) { 9 | 10 | try { 11 | return classloader.loadClass("ch.qos.logback.classic.LoggerContext") != null; 12 | } catch (ClassNotFoundException e) { 13 | return false; 14 | } 15 | } 16 | 17 | public static boolean isLog4j2Usable(ClassLoader classloader) { 18 | 19 | try { 20 | return (classloader.loadClass("org.apache.logging.slf4j.Log4jLoggerFactory") != null); 21 | } catch (ClassNotFoundException e) { 22 | return false; 23 | } 24 | } 25 | 26 | public static Class loadAliyunLog4j2AppenderExist(ClassLoader classloader) { 27 | 28 | try { 29 | return classloader.loadClass("com.aliyun.openservices.log.log4j2.LoghubAppender"); 30 | } catch (ClassNotFoundException e) { 31 | return null; 32 | } 33 | } 34 | 35 | public static Class loadAliyunLogbackAppenderExist(ClassLoader classloader) { 36 | 37 | try { 38 | return classloader.loadClass("com.aliyun.openservices.log.logback.LoghubAppender"); 39 | } catch (ClassNotFoundException e) { 40 | return null; 41 | } 42 | } 43 | 44 | } 45 | -------------------------------------------------------------------------------- /src/main/resources/META-INF/spring.factories: -------------------------------------------------------------------------------- 1 | org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ 2 | cn.keking.common.log.LogAppenderAutoConfiguration -------------------------------------------------------------------------------- /src/main/resources/aliyunlog.properties: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kekingcn/aliyunlog-spring-boot-starter/a4c5e859d680d8d2b1d6384ebc93d0bd5bcec862/src/main/resources/aliyunlog.properties --------------------------------------------------------------------------------