├── .gitignore ├── LICENSE ├── README.md ├── api ├── log4j-api │ ├── pom.xml │ └── src │ │ └── main │ │ ├── java │ │ └── com │ │ │ └── itkevin │ │ │ └── log4j │ │ │ └── api │ │ │ ├── config │ │ │ └── Log4jConfig.java │ │ │ ├── filter │ │ │ └── Log4jFilter.java │ │ │ └── listener │ │ │ └── Log4jApplicationListener.java │ │ └── resources │ │ └── META-INF │ │ ├── dubbo │ │ └── org.apache.dubbo.rpc.Filter │ │ └── spring.factories ├── logback-api │ ├── pom.xml │ └── src │ │ └── main │ │ ├── java │ │ └── com │ │ │ └── itkevin │ │ │ └── logback │ │ │ └── api │ │ │ ├── config │ │ │ └── LogbackConfig.java │ │ │ ├── filter │ │ │ └── LogbackFilter.java │ │ │ └── listener │ │ │ └── LogbackApplicationListener.java │ │ └── resources │ │ └── META-INF │ │ ├── dubbo │ │ └── org.apache.dubbo.rpc.Filter │ │ └── spring.factories └── pom.xml ├── common-dubbo ├── pom.xml └── src │ └── main │ └── java │ └── com │ └── itkevin │ └── dubbo │ └── common │ └── filter │ ├── LogApacheDubboFilter.java │ └── LogApacheMDCClearDubboFilter.java ├── common-web ├── pom.xml └── src │ └── main │ ├── java │ └── com │ │ └── itkevin │ │ └── web │ │ └── common │ │ ├── config │ │ └── WebFilterConfig.java │ │ ├── filter │ │ └── LogWebFilter.java │ │ ├── util │ │ ├── CustomRequestWrapper.java │ │ ├── HttpIPUtils.java │ │ └── HttpRequestUtil.java │ │ └── web │ │ └── WebInitializer.java │ └── resources │ └── META-INF │ └── spring.factories ├── common ├── pom.xml └── src │ └── main │ ├── java │ └── com │ │ └── itkevin │ │ └── common │ │ ├── config │ │ ├── ApolloConfigTool.java │ │ ├── ConfigTool.java │ │ └── SysConfig.java │ │ ├── constants │ │ └── SysConstant.java │ │ ├── enums │ │ ├── AlarmToolEnum.java │ │ ├── BusinessTypeEnum.java │ │ ├── LogConstantEnum.java │ │ ├── LogLevelEnum.java │ │ ├── LogTypeEnum.java │ │ ├── MDCConstantEnum.java │ │ └── RequestTypeEnum.java │ │ ├── listener │ │ └── ConfigListener.java │ │ ├── model │ │ ├── BusinessData.java │ │ ├── FilterMessage.java │ │ ├── HashedWheelData.java │ │ ├── LogCompressData.java │ │ ├── LogData.java │ │ ├── LogUriElapsedData.java │ │ ├── TaskModel.java │ │ ├── UriElapsedCollect.java │ │ └── UriElapsedData.java │ │ ├── notice │ │ ├── MarkDownBaseMessage.java │ │ ├── NoticeInterface.java │ │ ├── NotifyMessageTools.java │ │ ├── dingding │ │ │ ├── DingConfigData.java │ │ │ ├── DingMarkDownMessage.java │ │ │ ├── DingTalkNotice.java │ │ │ ├── DingTalkService.java │ │ │ └── DingTextMessage.java │ │ ├── model │ │ │ └── BaseMessage.java │ │ └── workwx │ │ │ ├── WeWorkConfigData.java │ │ │ ├── WeWorkMarkDownMessage.java │ │ │ ├── WeWorkTextMessage.java │ │ │ ├── WorkWeiXinTalkNotice.java │ │ │ └── WorkWeiXinTalkService.java │ │ └── util │ │ ├── BatchConsumerCallbackAdvisor.java │ │ ├── ByteBuddyPlugin.java │ │ ├── ByteBuddyUtils.java │ │ ├── CommonConverter.java │ │ ├── ConfigUtils.java │ │ ├── ConsumerCallbackAdvisor.java │ │ ├── ElapsedUtils.java │ │ ├── EventProcessorAdvisor.java │ │ ├── GuavaCacheUtils.java │ │ ├── HashedWheelTask.java │ │ ├── HashedWheelUtils.java │ │ ├── IPUtils.java │ │ ├── JobHandlerAdvisor.java │ │ ├── LocalCacheUtils.java │ │ ├── LogUtils.java │ │ ├── MD5Utils.java │ │ ├── MDCUtils.java │ │ ├── PropertiesUtils.java │ │ ├── SimpleMetricsUtils.java │ │ └── StringConverterFactory.java │ └── resources │ ├── META-INF │ └── services │ │ ├── com.itkevin.common.config.ConfigTool │ │ └── com.itkevin.common.notice.NoticeInterface │ ├── application.properties │ └── logback.xml ├── img ├── img.png ├── 主要特性.png └── 报警种类.png ├── pom.xml └── userManual.md /.gitignore: -------------------------------------------------------------------------------- 1 | HELP.md 2 | target/ 3 | !.mvn/wrapper/maven-wrapper.jar 4 | !**/src/main/** 5 | !**/src/test/** 6 | 7 | ### STS ### 8 | .apt_generated 9 | .classpath 10 | .factorypath 11 | .project 12 | .settings 13 | .springBeans 14 | .sts4-cache 15 | 16 | ### IntelliJ IDEA ### 17 | .idea 18 | *.iws 19 | *.iml 20 | *.ipr 21 | 22 | ### NetBeans ### 23 | /nbproject/private/ 24 | /nbbuild/ 25 | /dist/ 26 | /nbdist/ 27 | /.nb-gradle/ 28 | build/ 29 | logs/ 30 | 31 | ### VS Code ### 32 | .vscode/ -------------------------------------------------------------------------------- /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 |
2 |

📝sky-eye

3 | 4 | 5 | 6 |
7 | 8 | ## sky-eye:实时日志报警系统 9 | 10 | ### sky-eye是什么 11 | 目前市面上的日志预警系统绝大部分都是基于队列消费的方式进行异步处理,报警多数有延迟而且不够明确, 12 | 针对这个问题,我设计了一套基于日志filter的准实时报警系统,来帮助项目开发维护人员能够更加快速的 13 | 感知项目中的各种error日志,进而保障系统稳定性。 14 | 15 | 16 | ### sky-eye 架构设计 17 | sky-eye的主要特性: 18 | ![img.png](img/主要特性.png) 19 | 流程: 20 | ![img.png](img/img.png) 21 | 22 | ### 快速接入 23 | 24 | #### 1、引入包 25 | ```xml 26 | 27 | 28 | com.itkevin 29 | log4j-api 30 | 1.0.0 31 | 32 | 33 | 34 | 35 | com.itkevin 36 | logback-api 37 | 1.0.0 38 | 39 | ``` 40 | 41 | #### 2、设置设置启动参数 42 | ```java 43 | // log4j 44 | com.itkevin.log4j.api.listener.Log4jApplicationListener 45 | // logback 46 | com.itkevin.logback.api.listener.LogbackApplicationListener 47 | // 以上两个类交由spring管理,springboot项目通过自动配置的方式默认管理,不用其余配置 48 | ``` 49 | #### 3、apollo配置参数 50 | 首先需要创建对应的namespace名称为:skyeye 51 | ```properties 52 | # 是否启动报警 53 | skyeye.log.alarm.enabled = true 54 | # 报警钉钉严重错误机器人配置(支持多个机器人) 55 | skyeye.log.alarm.serious.talk.hook = [ { "webHook": "https://oapi.dingtalk.com/robot/send?access_token=xxxxx", "secret": "xxxx" } ] 56 | # 报警钉钉机器人配置(支持多个机器人) 57 | skyeye.log.alarm.talk.hook = [ { "webHook": "https://oapi.dingtalk.com/robot/send?access_token=xxx", "secret": "xxxx" } ] 58 | # 堆栈行数配置 59 | skyeye.log.alarm.stackNum = 10 60 | # 单条报警白名单 61 | skyeye.log.alarm.white.list = 我是白名单 62 | # 聚合报警白名单 63 | skyeye.log.alarm.aggre.white.list = 我是聚合白名单 64 | # 报警间隔时间(单位分钟) 65 | skyeye.log.alarm.notify.time = 1 66 | # 报警次数阀值 67 | skyeye.log.alarm.notify.count = 1 68 | # 接口耗时报警间隔时间(单位分钟) 69 | skyeye.log.alarm.uri.elapsed.time = 1 70 | # 接口耗时超过阀值时间的次数阀值(阀值时间如果不指定则默认1000毫秒) 71 | skyeye.log.alarm.uri.elapsed.count = 10 72 | # 指定URI接口耗时时间阀值(单位毫秒,支持指定多个URI) 73 | skyeye.log.alarm.uri.elapsed = [{"uri":"/user/logTest","elapsed":2000}] 74 | # 指定接口耗时时间阀值(单位毫秒,全局指定,不配置默认1000毫秒) 75 | skyeye.log.alarm.uri.elapsed.global = 1000 76 | # 选择提醒工具 77 | skyeye.log.alarm.tool = wework 78 | # 设置需要监控的logAppender 79 | skyeye.log.appender = file 80 | ``` 81 | #### 4、通知格式 82 | ``` 83 | error信息:这是个错误的信息 84 | 服务名称:null 85 | 服务器IP:192.168.199.1 86 | 服务器hostname:localhost 87 | 发生时间:2021-08-22 16:56:06 88 | 请求类型:null 89 | 跟踪traceId:null 90 | 请求URI:null 91 | 异常信息:这是个exception 92 | 异常堆栈: 93 | 这是个堆栈信息 94 | ``` 95 | #### 5、自定义通知接入方法 96 | - 1、实现`com.itkevin.common.notice.NoticeInterfacee`类,实现`com.itkevin.common.notice.NoticeInterface#sendMessage`和`com.itkevin.common.notice.NoticeInterface#filterFlag`两个方法 97 | - 2、在apollo上配置`skyeye.log.alarm.tool`属性,属性值设置为`com.itkevin.common.notice.NoticeInterface#filterFlag`方法的返回值 98 | - 3、在resources目录下创建META-INF.services文件夹创建名为`com.itkevin.common.notice.NoticeInterface`的文件,并将自己的实现类全路径写到这个文件中 99 | - 备注:目前`com.itkevin.common.notice.NoticeInterface#sendMessage`中暂时只支持markdown格式,后期会开放自定义格式 100 | 101 | #### 6、自定义配置中心接入方法 102 | - 1、实现`com.itkevin.common.config.ConfigTool`接口 103 | - 2、`com.itkevin.common.config.ConfigTool#getConfig`方法作为配置写入方法,需要把所有的配置信息返回到map中 104 | - 3、`com.itkevin.common.config.ConfigTool#sortFlag`方法是选取配置的方法,系统默认使用Apollo配置中心返回为0,想要使用自己的配置只需要返回一个大于0的数值就可以 105 | - 4、在resources目录下创建META-INF.services文件夹创建名为`com.itkevin.common.config.ConfigTool`的文件,并将自己的实现类全路径写到这个文件中 106 | - 备注:选取配置中心的方法可能不是很优雅,后面版本可能会修改 107 | 108 | #### 7、建议配置 109 | 建议在maven配置中添加强制版本号插件 110 | ```xml 111 | 112 | org.apache.maven.plugins 113 | maven-enforcer-plugin 114 | 3.0.0-M2 115 | 116 | 117 | enforce-versions 118 | 119 | enforce 120 | 121 | 122 | 123 | 124 | true 125 | 请排除jar包冲突:okhttp:3.14.4以下版本,hutool-all:5.3.2以下版本,slf4j-log4j12:1.7.5以下版本 126 | 127 | com.squareup.okhttp3:okhttp:(,3.14.4) 128 | cn.hutool:hutool-all:(,5.3.2) 129 | org.slf4j:slf4j-log4j12:(,1.7.5) 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | ``` 138 | 139 | #### 备注 140 | - 目前接口超时报警仅支持web,后期会同步支持部分的rpc框架 141 | - 目前仅支持apollo配置和钉钉报警,后期会开放配置接口,可自行选择配置,报警接口也会支持企业微信和飞书,并且开放通知接口,可自定接入报警 142 | 143 | 144 | -------------------------------------------------------------------------------- /api/log4j-api/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | sky-eye-api 7 | com.itkevin 8 | 2.0 9 | 10 | 4.0.0 11 | 12 | log4j-api 13 | 2.0.0 14 | jar 15 | 16 | 17 | 8 18 | 8 19 | 2.0.0 20 | 1.2.17 21 | 22 | 23 | 24 | 25 | 26 | com.itkevin 27 | sky-eye-web-common 28 | ${skyeye-common.version} 29 | 30 | 31 | com.itkevin 32 | sky-eye-common 33 | ${skyeye-common.version} 34 | 35 | 36 | log4j 37 | log4j 38 | ${log4j.version} 39 | 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /api/log4j-api/src/main/java/com/itkevin/log4j/api/config/Log4jConfig.java: -------------------------------------------------------------------------------- 1 | package com.itkevin.log4j.api.config; 2 | 3 | import com.itkevin.log4j.api.listener.Log4jApplicationListener; 4 | import com.itkevin.web.common.filter.LogWebFilter; 5 | import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; 6 | import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; 7 | import org.springframework.boot.web.servlet.FilterRegistrationBean; 8 | import org.springframework.context.annotation.Bean; 9 | import org.springframework.context.annotation.Configuration; 10 | 11 | import javax.servlet.Filter; 12 | 13 | /** 14 | * log4j config 15 | */ 16 | @Configuration 17 | @ConditionalOnClass( 18 | value = {org.apache.log4j.Logger.class, org.apache.log4j.spi.Filter .class} 19 | ) 20 | public class Log4jConfig { 21 | 22 | @Bean 23 | @ConditionalOnMissingBean 24 | public Log4jApplicationListener logbackApplicationListener() { 25 | return new Log4jApplicationListener(); 26 | } 27 | 28 | } 29 | -------------------------------------------------------------------------------- /api/log4j-api/src/main/java/com/itkevin/log4j/api/filter/Log4jFilter.java: -------------------------------------------------------------------------------- 1 | package com.itkevin.log4j.api.filter; 2 | 3 | import cn.hutool.core.date.DateUtil; 4 | import com.itkevin.common.config.SysConfig; 5 | import com.itkevin.common.constants.SysConstant; 6 | import com.itkevin.common.enums.LogTypeEnum; 7 | import com.itkevin.common.model.FilterMessage; 8 | import com.itkevin.common.model.LogData; 9 | import com.itkevin.common.util.LocalCacheUtils; 10 | import com.itkevin.common.util.LogUtils; 11 | import com.itkevin.common.notice.NotifyMessageTools; 12 | import lombok.extern.slf4j.Slf4j; 13 | import org.apache.commons.lang3.exception.ExceptionUtils; 14 | import org.apache.log4j.Level; 15 | import org.apache.log4j.spi.Filter; 16 | import org.apache.log4j.spi.LoggingEvent; 17 | import org.apache.log4j.spi.ThrowableInformation; 18 | 19 | import java.util.Date; 20 | import java.util.Optional; 21 | 22 | /** 23 | * log4j Filter 24 | */ 25 | @Slf4j 26 | public class Log4jFilter extends Filter { 27 | 28 | @Override 29 | public int decide(LoggingEvent event) { 30 | try { 31 | // error日志 32 | if (Level.ERROR.equals(event.getLevel())) { 33 | if (SysConfig.instance.getAlarmEnabled()) { 34 | // error信息 35 | String msg = event.getRenderedMessage() != null ? event.getRenderedMessage().trim() : ""; 36 | // 获取异常 37 | Throwable ex = Optional.of(event).map(LoggingEvent::getThrowableInformation).map(ThrowableInformation::getThrowable).orElse(null); 38 | // 异常message、异常堆栈 39 | String message = ex != null ? ex.getMessage() : ""; 40 | String stackTrace = ex != null ? ExceptionUtils.getStackTrace(ex) : ""; 41 | // 过滤信息 42 | FilterMessage filterMessage = new FilterMessage(); 43 | filterMessage.setLogType(LogTypeEnum.LOG4J.name()); 44 | filterMessage.setMsg(msg); 45 | filterMessage.setMessage(message); 46 | filterMessage.setStackTrace(stackTrace); 47 | // 获取logData对象 48 | LogData logData = LogUtils.obtainLogData(LogTypeEnum.LOG4J.name(), msg, message, stackTrace); 49 | logData.setOccurrenceTime(DateUtil.formatDateTime(new Date(event.getTimeStamp()))); 50 | logData.setFilter(LogUtils.filter(filterMessage)); 51 | // 发送消息 52 | NotifyMessageTools.getInstance().sendMessage(logData); 53 | // 异常报警上报 54 | if (!logData.getFilter() && !SysConstant.WELCOME.equals(logData.getErrorMessage())) { 55 | LocalCacheUtils.incr(SysConstant.ALARM_METRIC_NAME); 56 | } 57 | } 58 | } 59 | } catch (Throwable e) { 60 | log.warn("log skyeye >>> Log4jFilter occur exception", e); 61 | } 62 | 63 | return Filter.NEUTRAL; 64 | } 65 | 66 | } 67 | -------------------------------------------------------------------------------- /api/log4j-api/src/main/java/com/itkevin/log4j/api/listener/Log4jApplicationListener.java: -------------------------------------------------------------------------------- 1 | package com.itkevin.log4j.api.listener; 2 | 3 | import com.ctrip.framework.apollo.Config; 4 | import com.google.common.collect.Lists; 5 | import com.itkevin.common.config.ConfigTool; 6 | import com.itkevin.common.config.SysConfig; 7 | import com.itkevin.common.constants.SysConstant; 8 | import com.itkevin.common.listener.ConfigListener; 9 | import com.itkevin.common.util.HashedWheelTask; 10 | import com.itkevin.log4j.api.filter.Log4jFilter; 11 | import lombok.extern.slf4j.Slf4j; 12 | import org.apache.log4j.Appender; 13 | import org.apache.log4j.Logger; 14 | import org.apache.log4j.spi.Filter; 15 | import org.springframework.context.ApplicationContext; 16 | import org.springframework.context.ApplicationListener; 17 | import org.springframework.context.event.ContextRefreshedEvent; 18 | import org.springframework.util.CollectionUtils; 19 | 20 | import java.util.Comparator; 21 | import java.util.List; 22 | import java.util.Map; 23 | import java.util.ServiceLoader; 24 | import java.util.Set; 25 | 26 | /** 27 | * log4j初始化 28 | */ 29 | @Slf4j 30 | public class Log4jApplicationListener implements ApplicationListener { 31 | 32 | @Override 33 | public void onApplicationEvent(ContextRefreshedEvent event) { 34 | // 配置放入缓存 35 | ServiceLoader serviceLoader = ServiceLoader.load(ConfigTool.class); 36 | List configTools = Lists.newArrayList(); 37 | for (ConfigTool configTool : serviceLoader) { 38 | configTools.add(configTool); 39 | } 40 | configTools.stream().max(Comparator.comparing(ConfigTool::sortFlag)).ifPresent(configTool -> { 41 | Map map = configTool.getConfig(); 42 | SysConfig.convertMap2SysConfig(map); 43 | }); 44 | 45 | // 设置log filter,初始化动作 46 | Logger logger = Logger.getRootLogger(); 47 | Appender fileAppender = logger.getAppender("file"); 48 | if (fileAppender != null) { 49 | Filter filter = fileAppender.getFilter(); 50 | if (!(filter instanceof Log4jFilter)) { 51 | fileAppender.addFilter(new Log4jFilter()); 52 | // 初始化任务调度器 53 | HashedWheelTask.init(); 54 | // 项目启动后发送欢迎语 55 | log.error(SysConstant.WELCOME); 56 | } 57 | } 58 | } 59 | 60 | } 61 | 62 | -------------------------------------------------------------------------------- /api/log4j-api/src/main/resources/META-INF/dubbo/org.apache.dubbo.rpc.Filter: -------------------------------------------------------------------------------- 1 | logApacheMDCClearDubboFilter=com.skyeye.k12.teacher.common.filter.LogApacheMDCClearDubboFilter 2 | logApacheDubboFilter=com.skyeye.k12.teacher.common.filter.LogApacheDubboFilter -------------------------------------------------------------------------------- /api/log4j-api/src/main/resources/META-INF/spring.factories: -------------------------------------------------------------------------------- 1 | org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ 2 | com.itkevin.log4j.api.config.Log4jConfig -------------------------------------------------------------------------------- /api/logback-api/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | sky-eye-api 7 | com.itkevin 8 | 2.0 9 | 10 | 4.0.0 11 | 12 | logback-api 13 | 2.0.0 14 | jar 15 | 16 | 17 | 8 18 | 8 19 | 2.0.0 20 | 1.2.3 21 | 22 | 23 | 24 | 25 | com.itkevin 26 | sky-eye-web-common 27 | ${skyeye-common.version} 28 | 29 | 30 | com.itkevin 31 | sky-eye-common 32 | ${skyeye-common.version} 33 | 34 | 35 | slf4j-api 36 | org.slf4j 37 | 38 | 39 | 40 | 41 | ch.qos.logback 42 | logback-classic 43 | ${logback-classic.version} 44 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /api/logback-api/src/main/java/com/itkevin/logback/api/config/LogbackConfig.java: -------------------------------------------------------------------------------- 1 | package com.itkevin.logback.api.config; 2 | 3 | import com.itkevin.logback.api.listener.LogbackApplicationListener; 4 | import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; 5 | import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; 6 | import org.springframework.context.annotation.Bean; 7 | import org.springframework.context.annotation.Configuration; 8 | 9 | /** 10 | * log config 11 | */ 12 | @Configuration 13 | @ConditionalOnClass( 14 | value = {ch.qos.logback.classic.Logger.class, ch.qos.logback.core.filter.Filter.class} 15 | ) 16 | public class LogbackConfig { 17 | 18 | @Bean 19 | @ConditionalOnMissingBean 20 | public LogbackApplicationListener logbackApplicationListener() { 21 | return new LogbackApplicationListener(); 22 | } 23 | 24 | } 25 | -------------------------------------------------------------------------------- /api/logback-api/src/main/java/com/itkevin/logback/api/filter/LogbackFilter.java: -------------------------------------------------------------------------------- 1 | package com.itkevin.logback.api.filter; 2 | 3 | import ch.qos.logback.classic.Level; 4 | import ch.qos.logback.classic.spi.ILoggingEvent; 5 | import ch.qos.logback.classic.spi.IThrowableProxy; 6 | import ch.qos.logback.classic.spi.ThrowableProxy; 7 | import ch.qos.logback.core.filter.Filter; 8 | import ch.qos.logback.core.spi.FilterReply; 9 | import cn.hutool.core.date.DateUtil; 10 | import com.itkevin.common.config.SysConfig; 11 | import com.itkevin.common.constants.SysConstant; 12 | import com.itkevin.common.enums.LogTypeEnum; 13 | import com.itkevin.common.model.FilterMessage; 14 | import com.itkevin.common.model.LogData; 15 | import com.itkevin.common.util.LocalCacheUtils; 16 | import com.itkevin.common.util.LogUtils; 17 | import com.itkevin.common.notice.NotifyMessageTools; 18 | import lombok.extern.slf4j.Slf4j; 19 | import org.apache.commons.lang3.exception.ExceptionUtils; 20 | 21 | import java.util.Date; 22 | 23 | /** 24 | * logback filter 25 | */ 26 | @Slf4j 27 | public class LogbackFilter extends Filter { 28 | 29 | @Override 30 | public FilterReply decide(ILoggingEvent event) { 31 | try { 32 | // error日志 33 | if (Level.ERROR.equals(event.getLevel())) { 34 | if (SysConfig.instance.getAlarmEnabled()) { 35 | // error信息 36 | String msg = event.getFormattedMessage() != null ? event.getFormattedMessage().trim() : ""; 37 | // 异常message、异常堆栈 38 | String message = ""; 39 | String stackTrace = ""; 40 | // 获取异常 41 | IThrowableProxy iThrowableProxy = event.getThrowableProxy(); 42 | if (iThrowableProxy instanceof ThrowableProxy) { 43 | ThrowableProxy throwableProxy = (ThrowableProxy) iThrowableProxy; 44 | Throwable ex = throwableProxy.getThrowable(); 45 | message = ex.getMessage(); 46 | stackTrace = ExceptionUtils.getStackTrace(ex); 47 | } 48 | // 过滤信息 49 | FilterMessage filterMessage = new FilterMessage(); 50 | filterMessage.setLogType(LogTypeEnum.LOGBACK.name()); 51 | filterMessage.setMsg(msg); 52 | filterMessage.setMessage(message); 53 | filterMessage.setStackTrace(stackTrace); 54 | // 获取logData对象 55 | LogData logData = LogUtils.obtainLogData(LogTypeEnum.LOGBACK.name(), msg, message, stackTrace); 56 | logData.setOccurrenceTime(DateUtil.formatDateTime(new Date(event.getTimeStamp()))); 57 | logData.setFilter(LogUtils.filter(filterMessage)); 58 | // 发送消息 59 | NotifyMessageTools.getInstance().sendMessage(logData); 60 | // 异常报警上报 61 | if (!logData.getFilter() && !SysConstant.WELCOME.equals(logData.getErrorMessage())) { 62 | LocalCacheUtils.incr(SysConstant.ALARM_METRIC_NAME); 63 | } 64 | } 65 | } 66 | } catch (Throwable e) { 67 | log.warn("log skyeye >>> LogbackFilter occur exception", e); 68 | } 69 | 70 | return FilterReply.NEUTRAL; 71 | } 72 | 73 | } 74 | -------------------------------------------------------------------------------- /api/logback-api/src/main/java/com/itkevin/logback/api/listener/LogbackApplicationListener.java: -------------------------------------------------------------------------------- 1 | package com.itkevin.logback.api.listener; 2 | 3 | import ch.qos.logback.classic.Logger; 4 | import ch.qos.logback.classic.LoggerContext; 5 | import ch.qos.logback.classic.spi.ILoggingEvent; 6 | import ch.qos.logback.core.Appender; 7 | import ch.qos.logback.core.filter.Filter; 8 | import com.google.common.collect.Lists; 9 | import com.itkevin.common.config.ConfigTool; 10 | import com.itkevin.common.config.SysConfig; 11 | import com.itkevin.common.constants.SysConstant; 12 | import com.itkevin.common.util.HashedWheelTask; 13 | import com.itkevin.logback.api.filter.LogbackFilter; 14 | import lombok.extern.slf4j.Slf4j; 15 | import org.slf4j.LoggerFactory; 16 | import org.springframework.context.ApplicationListener; 17 | import org.springframework.context.event.ContextRefreshedEvent; 18 | 19 | import java.util.Comparator; 20 | import java.util.List; 21 | import java.util.Map; 22 | import java.util.ServiceLoader; 23 | 24 | /** 25 | * logback初始化 26 | */ 27 | @Slf4j 28 | public class LogbackApplicationListener implements ApplicationListener { 29 | 30 | @Override 31 | public void onApplicationEvent(ContextRefreshedEvent event) { 32 | // 配置放入缓存 33 | ServiceLoader serviceLoader = ServiceLoader.load(ConfigTool.class); 34 | List configTools = Lists.newArrayList(); 35 | for (ConfigTool configTool : serviceLoader) { 36 | configTools.add(configTool); 37 | } 38 | configTools.stream().max(Comparator.comparing(ConfigTool::sortFlag)).ifPresent(configTool -> { 39 | Map map = configTool.getConfig(); 40 | SysConfig.convertMap2SysConfig(map); 41 | }); 42 | 43 | // 设置log filter,初始化动作 44 | LoggerContext context = (LoggerContext) LoggerFactory.getILoggerFactory(); 45 | Logger logger = context.getLogger(Logger.ROOT_LOGGER_NAME); 46 | Appender fileAppenderERROR = logger.getAppender("console"); 47 | Appender fileAppender = fileAppenderERROR == null ? logger.getAppender(SysConfig.instance.getSkyeyeLogAppender()) : fileAppenderERROR; 48 | if (fileAppender != null) { 49 | List> filters = fileAppender.getCopyOfAttachedFiltersList(); 50 | for (Filter filter : filters) { 51 | if (filter instanceof LogbackFilter) { 52 | return; 53 | } 54 | } 55 | fileAppender.addFilter(new LogbackFilter()); 56 | // 初始化任务调度器 57 | HashedWheelTask.init(); 58 | // 项目启动后发送欢迎语 59 | log.error(SysConstant.WELCOME); 60 | } 61 | } 62 | 63 | } 64 | 65 | -------------------------------------------------------------------------------- /api/logback-api/src/main/resources/META-INF/dubbo/org.apache.dubbo.rpc.Filter: -------------------------------------------------------------------------------- 1 | logApacheMDCClearDubboFilter=com.skyeye.k12.teacher.common.filter.LogApacheMDCClearDubboFilter 2 | logApacheDubboFilter=com.skyeye.k12.teacher.common.filter.LogApacheDubboFilter -------------------------------------------------------------------------------- /api/logback-api/src/main/resources/META-INF/spring.factories: -------------------------------------------------------------------------------- 1 | org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ 2 | com.itkevin.logback.api.config.LogbackConfig -------------------------------------------------------------------------------- /api/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | sky-eye 7 | com.itkevin 8 | 2.0 9 | 10 | 4.0.0 11 | 12 | sky-eye-api 13 | pom 14 | 2.0 15 | 16 | 17 | log4j-api 18 | logback-api 19 | 20 | 21 | 22 | 8 23 | 8 24 | 25 | 26 | -------------------------------------------------------------------------------- /common-dubbo/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | sky-eye 7 | com.itkevin 8 | 2.0 9 | 10 | 4.0.0 11 | 2.0.0 12 | jar 13 | 14 | sky-eye-dubbo-common 15 | 16 | 17 | 8 18 | 8 19 | 2.0.0 20 | 21 | 22 | 23 | 24 | 25 | com.itkevin 26 | sky-eye-common 27 | ${skyeye-common.version} 28 | 29 | 30 | 31 | org.apache.dubbo 32 | dubbo 33 | 2.7.7 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /common-dubbo/src/main/java/com/itkevin/dubbo/common/filter/LogApacheDubboFilter.java: -------------------------------------------------------------------------------- 1 | package com.itkevin.dubbo.common.filter; 2 | 3 | import com.itkevin.common.enums.MDCConstantEnum; 4 | import com.itkevin.common.enums.RequestTypeEnum; 5 | import com.itkevin.common.model.UriElapsedCollect; 6 | import com.itkevin.common.util.ElapsedUtils; 7 | import com.itkevin.common.util.MDCUtils; 8 | import lombok.extern.slf4j.Slf4j; 9 | import org.apache.dubbo.common.constants.CommonConstants; 10 | import org.apache.dubbo.common.extension.Activate; 11 | import org.apache.dubbo.rpc.Filter; 12 | import org.apache.dubbo.rpc.Invocation; 13 | import org.apache.dubbo.rpc.Invoker; 14 | import org.apache.dubbo.rpc.Result; 15 | import org.apache.dubbo.rpc.RpcContext; 16 | import org.apache.dubbo.rpc.RpcException; 17 | import org.apache.skywalking.apm.toolkit.trace.TraceContext; 18 | 19 | import java.util.Arrays; 20 | 21 | @Slf4j 22 | @Activate( 23 | group = {CommonConstants.PROVIDER_SIDE}, 24 | order = 1 25 | ) 26 | public class LogApacheDubboFilter implements Filter { 27 | 28 | @Override 29 | public Result invoke(Invoker invoker, Invocation invocation) throws RpcException { 30 | long start = 0; 31 | try { 32 | mdc(invoker, invocation); 33 | start = System.currentTimeMillis(); 34 | return invoker.invoke(invocation); 35 | } finally { 36 | UriElapsedCollect uriElapsedCollect = ElapsedUtils.uriElapsedCollect(System.currentTimeMillis() - start); 37 | ElapsedUtils.uriElapsed(uriElapsedCollect); 38 | } 39 | } 40 | 41 | /** 42 | * mdc上下文 43 | * @param invoker 44 | * @param invocation 45 | */ 46 | private void mdc(Invoker invoker, Invocation invocation) { 47 | try { 48 | RpcContext rpcContext = RpcContext.getContext(); 49 | MDCUtils.put(MDCConstantEnum.SERVER_NAME.getCode(), rpcContext.getUrl().getParameter("application")); 50 | MDCUtils.put(MDCConstantEnum.SOURCE_IP.getCode(), rpcContext.getRemoteHost()); 51 | MDCUtils.put(MDCConstantEnum.SERVER_IP.getCode(), rpcContext.getLocalHost()); 52 | // MDCUtils.put(MDCConstantEnum.SERVER_HOSTNAME.getCode(), IPUtils.getLocalHostName()); 53 | MDCUtils.put(MDCConstantEnum.REQUEST_TYPE.getCode(), RequestTypeEnum.DUBBO.name().toLowerCase()); 54 | MDCUtils.put(MDCConstantEnum.TRACE_ID.getCode(), TraceContext.traceId()); 55 | MDCUtils.put(MDCConstantEnum.REQUEST_URI.getCode(), invoker.getInterface().getName() + "#" + invocation.getMethodName()); 56 | MDCUtils.put(MDCConstantEnum.REQUEST_PARAM.getCode(), Arrays.asList(invocation.getArguments()).toString()); 57 | } catch (Exception e) { 58 | log.warn("log skyeye >>> LogApacheDubboFilter occur exception", e); 59 | } 60 | } 61 | 62 | } 63 | -------------------------------------------------------------------------------- /common-dubbo/src/main/java/com/itkevin/dubbo/common/filter/LogApacheMDCClearDubboFilter.java: -------------------------------------------------------------------------------- 1 | package com.itkevin.dubbo.common.filter; 2 | 3 | import com.itkevin.common.util.MDCUtils; 4 | import lombok.extern.slf4j.Slf4j; 5 | import org.apache.dubbo.common.constants.CommonConstants; 6 | import org.apache.dubbo.common.extension.Activate; 7 | import org.apache.dubbo.rpc.*; 8 | 9 | @Slf4j 10 | @Activate( 11 | group = {CommonConstants.PROVIDER_SIDE}, 12 | order = -1 13 | ) 14 | public class LogApacheMDCClearDubboFilter implements Filter { 15 | 16 | @Override 17 | public Result invoke(Invoker invoker, Invocation invocation) throws RpcException { 18 | try { 19 | return invoker.invoke(invocation); 20 | } finally { 21 | MDCUtils.clear(); 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /common-web/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | sky-eye 7 | com.itkevin 8 | 2.0 9 | 10 | 4.0.0 11 | 2.0.0 12 | jar 13 | 14 | sky-eye-web-common 15 | 16 | 17 | 8 18 | 8 19 | 2.0.0 20 | 21 | 22 | 23 | 24 | com.itkevin 25 | sky-eye-common 26 | ${skyeye-common.version} 27 | 28 | 29 | okhttp 30 | com.squareup.okhttp3 31 | 32 | 33 | gson 34 | com.google.code.gson 35 | 36 | 37 | retrofit 38 | com.squareup.retrofit2 39 | 40 | 41 | slf4j-api 42 | org.slf4j 43 | 44 | 45 | reactor-core 46 | io.projectreactor 47 | 48 | 49 | 50 | 51 | 52 | org.springframework.boot 53 | spring-boot-starter-web 54 | 2.1.6.RELEASE 55 | 56 | 57 | 58 | javax.servlet 59 | servlet-api 60 | 2.5 61 | provided 62 | 63 | 64 | -------------------------------------------------------------------------------- /common-web/src/main/java/com/itkevin/web/common/config/WebFilterConfig.java: -------------------------------------------------------------------------------- 1 | package com.itkevin.web.common.config; 2 | 3 | import com.itkevin.web.common.filter.LogWebFilter; 4 | import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; 5 | import org.springframework.boot.web.servlet.FilterRegistrationBean; 6 | import org.springframework.context.annotation.Bean; 7 | import org.springframework.context.annotation.Configuration; 8 | 9 | import javax.servlet.Filter; 10 | 11 | @Configuration 12 | @ConditionalOnClass( 13 | value = {ch.qos.logback.classic.Logger.class, ch.qos.logback.core.filter.Filter.class} 14 | ) 15 | public class WebFilterConfig { 16 | 17 | @Bean 18 | public Filter logWebFilter() { 19 | return new LogWebFilter(); 20 | } 21 | 22 | @Bean 23 | public FilterRegistrationBean logWebFilterBean() { 24 | FilterRegistrationBean registration = new FilterRegistrationBean(); 25 | registration.setFilter(logWebFilter()); 26 | registration.addUrlPatterns("/*"); 27 | return registration; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /common-web/src/main/java/com/itkevin/web/common/filter/LogWebFilter.java: -------------------------------------------------------------------------------- 1 | package com.itkevin.web.common.filter; 2 | 3 | import com.itkevin.common.model.UriElapsedCollect; 4 | import com.itkevin.web.common.util.CustomRequestWrapper; 5 | import com.itkevin.common.util.ElapsedUtils; 6 | import com.itkevin.web.common.util.HttpRequestUtil; 7 | import com.itkevin.common.util.MDCUtils; 8 | import org.apache.commons.lang3.StringUtils; 9 | import org.springframework.http.MediaType; 10 | 11 | import javax.servlet.Filter; 12 | import javax.servlet.FilterChain; 13 | import javax.servlet.FilterConfig; 14 | import javax.servlet.ServletException; 15 | import javax.servlet.ServletRequest; 16 | import javax.servlet.ServletResponse; 17 | import javax.servlet.http.HttpServletRequest; 18 | import java.io.IOException; 19 | 20 | /** 21 | * log servlet filter 22 | */ 23 | public class LogWebFilter implements Filter { 24 | 25 | @Override 26 | public void init(FilterConfig filterConfig) throws ServletException { 27 | 28 | } 29 | 30 | @Override 31 | public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { 32 | long start = 0; 33 | try { 34 | HttpServletRequest request = (HttpServletRequest) servletRequest; 35 | String contentType = request.getContentType(); 36 | if (StringUtils.isNotBlank(contentType) && contentType.contains(MediaType.MULTIPART_FORM_DATA_VALUE)) { 37 | HttpRequestUtil.mdc(request); 38 | start = System.currentTimeMillis(); 39 | filterChain.doFilter(servletRequest, servletResponse); 40 | } else { 41 | CustomRequestWrapper requestWrapper = new CustomRequestWrapper(request); 42 | HttpRequestUtil.mdc(requestWrapper); 43 | start = System.currentTimeMillis(); 44 | filterChain.doFilter(requestWrapper, servletResponse); 45 | } 46 | } finally { 47 | UriElapsedCollect uriElapsedCollect = ElapsedUtils.uriElapsedCollect(System.currentTimeMillis() - start); 48 | ElapsedUtils.uriElapsed(uriElapsedCollect); 49 | MDCUtils.clear(); 50 | } 51 | } 52 | 53 | @Override 54 | public void destroy() { 55 | 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /common-web/src/main/java/com/itkevin/web/common/util/CustomRequestWrapper.java: -------------------------------------------------------------------------------- 1 | package com.itkevin.web.common.util; 2 | 3 | import org.apache.commons.lang3.StringUtils; 4 | 5 | import javax.servlet.ReadListener; 6 | import javax.servlet.ServletInputStream; 7 | import javax.servlet.http.HttpServletRequest; 8 | import javax.servlet.http.HttpServletRequestWrapper; 9 | import java.io.BufferedReader; 10 | import java.io.ByteArrayInputStream; 11 | import java.io.IOException; 12 | import java.io.InputStreamReader; 13 | import java.nio.charset.Charset; 14 | import java.util.Enumeration; 15 | 16 | public class CustomRequestWrapper extends HttpServletRequestWrapper { 17 | private final byte[] paramArray; 18 | 19 | private final String paramStr; 20 | 21 | /** 22 | * Constructs a request object wrapping the given request. 23 | * 24 | * @param request 25 | * @throws IllegalArgumentException if the request is null 26 | */ 27 | public CustomRequestWrapper(HttpServletRequest request) { 28 | super(request); 29 | paramStr = HttpRequestUtil.getParamString(request); 30 | paramArray = paramStr.getBytes(Charset.forName("UTF-8")); 31 | } 32 | 33 | public String getParamStr() { 34 | return paramStr; 35 | } 36 | 37 | 38 | @Override 39 | public BufferedReader getReader() throws IOException { 40 | if (StringUtils.isBlank(paramStr)) { 41 | return super.getReader(); 42 | } 43 | return new BufferedReader(new InputStreamReader(getInputStream())); 44 | } 45 | 46 | @Override 47 | public ServletInputStream getInputStream() throws IOException { 48 | if (StringUtils.isBlank(paramStr)) { 49 | return super.getInputStream(); 50 | } 51 | final ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(paramArray); 52 | return new ServletInputStream() { 53 | @Override 54 | public boolean isFinished() { 55 | return false; 56 | } 57 | 58 | @Override 59 | public boolean isReady() { 60 | return true; 61 | } 62 | 63 | @Override 64 | public void setReadListener(ReadListener readListener) { 65 | 66 | } 67 | 68 | @Override 69 | public int read() throws IOException { 70 | return byteArrayInputStream.read(); 71 | } 72 | }; 73 | } 74 | 75 | @Override 76 | public String getHeader(String name) { 77 | return super.getHeader(name); 78 | } 79 | 80 | @Override 81 | public Enumeration getHeaderNames() { 82 | return super.getHeaderNames(); 83 | } 84 | 85 | @Override 86 | public Enumeration getHeaders(String name) { 87 | return super.getHeaders(name); 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /common-web/src/main/java/com/itkevin/web/common/util/HttpIPUtils.java: -------------------------------------------------------------------------------- 1 | package com.itkevin.web.common.util; 2 | 3 | import org.apache.commons.lang3.StringUtils; 4 | 5 | import javax.servlet.http.HttpServletRequest; 6 | import java.io.IOException; 7 | import java.io.InputStream; 8 | import java.net.InetAddress; 9 | import java.net.NetworkInterface; 10 | import java.net.UnknownHostException; 11 | import java.util.Enumeration; 12 | 13 | public class HttpIPUtils { 14 | private static String hostName; 15 | 16 | /** 17 | * 获取请求IP 18 | * @param request 19 | * @return 20 | */ 21 | public static String getIPAddress(HttpServletRequest request) { 22 | if (request == null) { 23 | return "unknown"; 24 | } 25 | String ip = request.getHeader("x-forwarded-for"); 26 | if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { 27 | ip = request.getHeader("Proxy-Client-IP"); 28 | } 29 | if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { 30 | ip = request.getHeader("X-Forwarded-For"); 31 | } 32 | if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { 33 | ip = request.getHeader("WL-Proxy-Client-IP"); 34 | } 35 | if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { 36 | ip = request.getHeader("X-Real-IP"); 37 | } 38 | 39 | if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { 40 | ip = request.getRemoteAddr(); 41 | if (ip.equals("127.0.0.1") || ip.equals("0:0:0:0:0:0:0:1")) { 42 | // 根据网卡取本机配置的IP 43 | InetAddress inet = null; 44 | try { 45 | inet = InetAddress.getLocalHost(); 46 | } catch (UnknownHostException e) { 47 | e.printStackTrace(); 48 | } 49 | ip = inet.getHostAddress(); 50 | } 51 | } 52 | // 对于通过多个代理的情况,第一个IP为客户端真实IP,多个IP按照','分割 53 | if (ip != null && ip.length() > 15) { 54 | if (ip.indexOf(",") > 0) { 55 | ip = ip.substring(0, ip.indexOf(",")); 56 | } 57 | } 58 | 59 | return ip; 60 | } 61 | 62 | /** 63 | * 获取本机ip 64 | * @return 65 | */ 66 | public static String getLocalIp() { 67 | try { 68 | //一个主机有多个网络接口 69 | Enumeration netInterfaces = NetworkInterface.getNetworkInterfaces(); 70 | while (netInterfaces.hasMoreElements()) { 71 | NetworkInterface netInterface = netInterfaces.nextElement(); 72 | //每个网络接口,都会有多个"网络地址",比如一定会有loopback地址,会有siteLocal地址等.以及IPV4或者IPV6 . 73 | Enumeration addresses = netInterface.getInetAddresses(); 74 | while (addresses.hasMoreElements()) { 75 | InetAddress address = addresses.nextElement(); 76 | //get only :172.*,192.*,10.* 77 | if (address.isSiteLocalAddress() && !address.isLoopbackAddress()) { 78 | return address.getHostAddress(); 79 | } 80 | } 81 | } 82 | }catch (Exception e) { 83 | e.printStackTrace(); 84 | return null; 85 | } 86 | return null; 87 | } 88 | 89 | /** 90 | * 获取本机hostname 91 | * @return 92 | */ 93 | public static String getLocalHostName() { 94 | if (StringUtils.isNotBlank(hostName)) { 95 | return hostName; 96 | } 97 | String hostname = null; 98 | try { 99 | hostname = InetAddress.getLocalHost().getHostName(); 100 | } catch (UnknownHostException uhe) { 101 | String host = uhe.getMessage(); 102 | if (host != null) { 103 | int colon = host.indexOf(':'); 104 | if (colon > 0) { 105 | return host.substring(0, colon); 106 | } 107 | } 108 | } 109 | if (StringUtils.isNotBlank(hostname) && hostname.contains("-")) { 110 | hostName = hostname; 111 | return hostname; 112 | } 113 | InputStream is = null; 114 | try { 115 | Process p = Runtime.getRuntime().exec("hostname"); 116 | byte[] hostBytes = new byte[1024]; 117 | is = p.getInputStream(); 118 | int readed = is.read(hostBytes); 119 | p.waitFor(); 120 | hostName = new String(hostBytes, 0, readed); 121 | if (StringUtils.isNotBlank(hostName)) { 122 | hostName = hostName.trim(); 123 | } 124 | return hostName; 125 | } catch (Throwable e) { 126 | e.printStackTrace(); 127 | } finally { 128 | if (is != null) { 129 | try { 130 | is.close(); 131 | } catch (IOException e) { 132 | e.printStackTrace(); 133 | } 134 | } 135 | } 136 | hostName = "unknownHostname"; 137 | return hostName; 138 | } 139 | 140 | } 141 | -------------------------------------------------------------------------------- /common-web/src/main/java/com/itkevin/web/common/util/HttpRequestUtil.java: -------------------------------------------------------------------------------- 1 | package com.itkevin.web.common.util; 2 | 3 | import com.itkevin.common.enums.MDCConstantEnum; 4 | import com.itkevin.common.util.MDCUtils; 5 | import lombok.extern.slf4j.Slf4j; 6 | import org.apache.skywalking.apm.toolkit.trace.TraceContext; 7 | import org.springframework.http.MediaType; 8 | 9 | import javax.servlet.ServletInputStream; 10 | import javax.servlet.http.HttpServletRequest; 11 | import java.io.BufferedReader; 12 | import java.io.IOException; 13 | import java.io.InputStreamReader; 14 | import java.nio.charset.Charset; 15 | import java.util.Map; 16 | 17 | @Slf4j 18 | public class HttpRequestUtil { 19 | 20 | /** 21 | * 获取请求参数 22 | * try-with-resource 执行完 try 块后会释放声明的资源(实现了 AutoCloseable 接口的类对象) 23 | * @param servletRequest 24 | * @return 25 | */ 26 | public static String getParamString(HttpServletRequest servletRequest) { 27 | String result = ""; 28 | String contentType = servletRequest.getContentType(); 29 | 30 | if(contentType==null) { 31 | Map parameterMap = servletRequest.getParameterMap(); 32 | StringBuilder stringBuilder = new StringBuilder(); 33 | for(String key : parameterMap.keySet()) { 34 | stringBuilder.append(convertStr(key,parameterMap.get(key))); 35 | } 36 | result = stringBuilder.toString(); 37 | } else { 38 | if (MediaType.APPLICATION_FORM_URLENCODED_VALUE.equalsIgnoreCase(contentType)) { 39 | Map parameterMap = servletRequest.getParameterMap(); 40 | StringBuilder stringBuilder = new StringBuilder(); 41 | for(String key : parameterMap.keySet()) { 42 | stringBuilder.append(convertStr(key,parameterMap.get(key))); 43 | } 44 | result = stringBuilder.toString(); 45 | } else if (MediaType.APPLICATION_JSON_VALUE.equalsIgnoreCase(contentType) 46 | || MediaType.APPLICATION_JSON_UTF8_VALUE.equalsIgnoreCase(contentType) 47 | || MediaType.TEXT_PLAIN_VALUE.equalsIgnoreCase(contentType)){ 48 | try (ServletInputStream inputStream = servletRequest.getInputStream(); 49 | BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream, Charset.forName("UTF-8"))) ) 50 | { 51 | StringBuilder sb = new StringBuilder(); 52 | String line; 53 | while ((line=reader.readLine())!=null) { 54 | sb.append(line); 55 | } 56 | result = sb.toString(); 57 | } catch (IOException e) { 58 | log.warn("log skyeye >>> Failed to get the binary data stream contained in the request body! Url is '{}'.", servletRequest.getRequestURL().toString(), e); 59 | } 60 | } 61 | } 62 | 63 | return result; 64 | } 65 | 66 | 67 | /** 68 | * 使用 , 连接字符串,末尾是空格 69 | * @param values 70 | * @param key 71 | * @return key=value1,value2,value3 72 | */ 73 | public static String convertStr(String key,String[] values) { 74 | if(values==null){ 75 | return null; 76 | } 77 | int length = values.length; 78 | StringBuilder stringBuilder = new StringBuilder(length); 79 | stringBuilder.append(key).append("="); 80 | for(int i=0;i>> LogWebFilter occur exception", e); 107 | } 108 | } 109 | 110 | } 111 | -------------------------------------------------------------------------------- /common-web/src/main/java/com/itkevin/web/common/web/WebInitializer.java: -------------------------------------------------------------------------------- 1 | package com.itkevin.web.common.web; 2 | 3 | import com.itkevin.web.common.filter.LogWebFilter; 4 | import org.springframework.web.WebApplicationInitializer; 5 | 6 | import javax.servlet.DispatcherType; 7 | import javax.servlet.FilterRegistration; 8 | import javax.servlet.ServletContext; 9 | import javax.servlet.ServletException; 10 | import java.util.EnumSet; 11 | 12 | /** 13 | * servlet初始化类 14 | */ 15 | public class WebInitializer implements WebApplicationInitializer { 16 | 17 | @Override 18 | public void onStartup(ServletContext servletContext) throws ServletException { 19 | FilterRegistration filterRegistration = servletContext.getFilterRegistration("LogWebFilter"); 20 | if (filterRegistration == null) { 21 | FilterRegistration.Dynamic logWebFilter = servletContext.addFilter("LogWebFilter", LogWebFilter.class); 22 | logWebFilter.addMappingForUrlPatterns(EnumSet.of(DispatcherType.REQUEST), true, "/*"); 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /common-web/src/main/resources/META-INF/spring.factories: -------------------------------------------------------------------------------- 1 | org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ 2 | com.itkevin.web.common.config.WebFilterConfig -------------------------------------------------------------------------------- /common/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | sky-eye 7 | com.itkevin 8 | 2.0 9 | 10 | 4.0.0 11 | 12 | sky-eye-common 13 | 2.0.0 14 | jar 15 | 16 | 17 | 8 18 | 8 19 | 1.5.4 20 | 6.6.0 21 | 5.3.2 22 | 3.8.1 23 | 1.4.6 24 | 2.7.1 25 | 3.2.11.RELEASE 26 | 2.1.0 27 | 1.0.0 28 | 2.0.2 29 | 1.3.3.RELEASE 30 | 2.1.0.14 31 | 32 | 33 | 34 | 35 | 36 | cn.hutool 37 | hutool-all 38 | ${hutool-all.version} 39 | 40 | 41 | 42 | org.apache.commons 43 | commons-lang3 44 | ${commons-lang3.version} 45 | 46 | 47 | 48 | io.projectreactor 49 | reactor-core 50 | ${reactor-core.version} 51 | 52 | 53 | 54 | net.bytebuddy 55 | byte-buddy 56 | 1.10.11 57 | 58 | 59 | 60 | net.bytebuddy 61 | byte-buddy-agent 62 | 1.10.11 63 | 64 | 65 | 66 | org.projectlombok 67 | lombok 68 | 1.18.20 69 | 70 | 71 | 72 | com.ctrip.framework.apollo 73 | apollo-client 74 | 1.6.2 75 | 76 | 77 | gson 78 | com.google.code.gson 79 | 80 | 81 | 82 | 83 | 84 | ma.glasnost.orika 85 | orika-core 86 | 1.4.3 87 | 88 | 89 | slf4j-api 90 | org.slf4j 91 | 92 | 93 | 94 | 95 | 96 | com.squareup.retrofit2 97 | retrofit 98 | 2.6.3 99 | 100 | 101 | okhttp 102 | com.squareup.okhttp3 103 | 104 | 105 | 106 | 107 | 108 | com.squareup.retrofit2 109 | converter-gson 110 | 2.7.0 111 | 112 | 113 | retrofit 114 | com.squareup.retrofit2 115 | 116 | 117 | 118 | 119 | 120 | com.jakewharton.retrofit 121 | retrofit2-rxjava2-adapter 122 | 1.0.0 123 | 124 | 125 | retrofit 126 | com.squareup.retrofit2 127 | 128 | 129 | reactive-streams 130 | org.reactivestreams 131 | 132 | 133 | 134 | 135 | 136 | io.micrometer 137 | micrometer-core 138 | 1.6.5 139 | 140 | 141 | 142 | org.apache.skywalking 143 | apm-toolkit-trace 144 | 8.7.0 145 | 146 | 147 | 148 | com.jakewharton.retrofit 149 | retrofit2-reactor-adapter 150 | ${retrofit2-reactor-adapter.version} 151 | 152 | 153 | retrofit 154 | com.squareup.retrofit2 155 | 156 | 157 | reactor-core 158 | io.projectreactor 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | org.apache.maven.plugins 168 | maven-compiler-plugin 169 | 170 | 1.8 171 | 1.8 172 | 173 | 174 | 175 | 176 | -------------------------------------------------------------------------------- /common/src/main/java/com/itkevin/common/config/ApolloConfigTool.java: -------------------------------------------------------------------------------- 1 | package com.itkevin.common.config; 2 | 3 | import cn.hutool.core.collection.CollectionUtil; 4 | import com.ctrip.framework.apollo.Config; 5 | import com.ctrip.framework.apollo.ConfigService; 6 | import com.google.common.collect.Maps; 7 | import com.itkevin.common.listener.ConfigListener; 8 | 9 | import java.util.Map; 10 | import java.util.Set; 11 | 12 | public class ApolloConfigTool implements ConfigTool{ 13 | 14 | @Override 15 | public Map getConfig() { 16 | // 添加apollo配置监听器,获取apollo配置放入缓存 17 | Map map = Maps.newHashMap(); 18 | Config config = ConfigService.getConfig("skyeye"); 19 | config.addChangeListener(new ConfigListener()); 20 | Set propertyNames = config.getPropertyNames(); 21 | if (!CollectionUtil.isEmpty(propertyNames)) { 22 | propertyNames.forEach(propertyName -> { 23 | String propertyValue = config.getProperty(propertyName, null); 24 | map.put(propertyName,propertyValue); 25 | }); 26 | } 27 | return map; 28 | } 29 | 30 | @Override 31 | public Integer sortFlag() { 32 | return 0; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /common/src/main/java/com/itkevin/common/config/ConfigTool.java: -------------------------------------------------------------------------------- 1 | package com.itkevin.common.config; 2 | 3 | import java.util.Map; 4 | 5 | /** 6 | * 开放接口配置 7 | */ 8 | public interface ConfigTool { 9 | 10 | /** 11 | * 获取配置 12 | * @return 13 | */ 14 | Map getConfig(); 15 | 16 | /** 17 | * 排序过滤器,选取数据最大的作为获取配置的途径 18 | * @return 19 | */ 20 | Integer sortFlag(); 21 | } 22 | -------------------------------------------------------------------------------- /common/src/main/java/com/itkevin/common/config/SysConfig.java: -------------------------------------------------------------------------------- 1 | package com.itkevin.common.config; 2 | 3 | import cn.hutool.json.JSONUtil; 4 | import com.itkevin.common.constants.SysConstant; 5 | import lombok.Getter; 6 | import org.apache.commons.lang3.StringUtils; 7 | 8 | import java.io.Serializable; 9 | import java.util.Map; 10 | import java.util.Objects; 11 | 12 | public class SysConfig implements Serializable { 13 | 14 | public static SysConfig instance = new SysConfig(); 15 | 16 | /** 17 | * log报警开启 18 | */ 19 | @Getter 20 | private Boolean alarmEnabled = false; 21 | 22 | /** 23 | * 报警机器人配置(支持多个机器人) 24 | */ 25 | @Getter 26 | private String alarmTalkHook = "{}"; 27 | 28 | /** 29 | * 报警严重错误机器人配置(支持多个机器人) 30 | */ 31 | @Getter 32 | private String alarmSeriousTalkHook = "{}"; 33 | 34 | /** 35 | * 堆栈行数配置 36 | */ 37 | @Getter 38 | private Integer alarmStacknum = 10; 39 | 40 | /** 41 | * 单条报警白名单 42 | */ 43 | @Getter 44 | private String alarmWhiteList = ""; 45 | 46 | /** 47 | * 聚合报警白名单 48 | */ 49 | @Getter 50 | private String alarmAggreWhiteList = ""; 51 | 52 | /** 53 | * 报警间隔时间(单位分钟) 54 | */ 55 | @Getter 56 | private Integer alarmNotifyTime = 1; 57 | 58 | /** 59 | * 报警次数阀值 60 | */ 61 | @Getter 62 | private Integer alarmNotifyCount = 5; 63 | 64 | /** 65 | * 接口耗时报警间隔时间(单位分钟) 66 | */ 67 | @Getter 68 | private Integer alarmUriElapsedTime = 1; 69 | 70 | /** 71 | * 接口耗时超过阀值时间的次数阀值(阀值时间如果不指定则默认1000毫秒) 72 | */ 73 | @Getter 74 | private Integer alarmUriElapsedCount = 1000; 75 | 76 | /** 77 | * 指定URI接口耗时时间阀值(单位毫秒,支持指定多个URI) 78 | */ 79 | @Getter 80 | private String alarmUriElapsed = "{}"; 81 | 82 | /** 83 | * 指定接口耗时时间阀值(单位毫秒,全局指定,不配置默认1000毫秒) 84 | */ 85 | @Getter 86 | private Long alarmUriElapsedGlobal = 1000L; 87 | 88 | /** 89 | * skyeye异常报警途径 90 | */ 91 | @Getter 92 | private String alarmTool = "wework"; 93 | 94 | /** 95 | * 需要监控的LogAppender名称 96 | */ 97 | @Getter 98 | private String skyeyeLogAppender = "file"; 99 | 100 | 101 | public static SysConfig convertMap2SysConfig(Map map){ 102 | String skyeyeLogAppenderStr = map.get(SysConstant.LOG_APPENDER); 103 | if(StringUtils.isNotBlank(skyeyeLogAppenderStr)){ 104 | instance.skyeyeLogAppender = skyeyeLogAppenderStr; 105 | } 106 | 107 | String enableStr = map.get(SysConstant.ALARM_ENABLED); 108 | if(!Objects.isNull(enableStr)){ 109 | instance.alarmEnabled = Boolean.parseBoolean(enableStr); 110 | } 111 | String alarmTalkStr = map.get(SysConstant.ALARM_DINGTALK); 112 | if(JSONUtil.isJson(alarmTalkStr)){ 113 | instance.alarmTalkHook = alarmTalkStr; 114 | } 115 | 116 | String alarmSeriousTalkStr = map.get(SysConstant.ALARM_SERIOUS_DINGTALK); 117 | if(JSONUtil.isJson(alarmSeriousTalkStr)){ 118 | instance.alarmSeriousTalkHook = alarmSeriousTalkStr; 119 | } 120 | 121 | String alarmStackNum = map.get(SysConstant.ALARM_STACKNUM); 122 | if(StringUtils.isNumeric(alarmStackNum)){ 123 | instance.alarmStacknum = Integer.parseInt(alarmStackNum); 124 | } 125 | 126 | String alarmWhiteList = map.get(SysConstant.ALARM_WHITE_LIST); 127 | if(StringUtils.isNotBlank(alarmWhiteList)){ 128 | instance.alarmWhiteList = alarmWhiteList; 129 | } 130 | String alarmAggreWhiteList = map.get(SysConstant.ALARM_AGGRE_WHITE_LIST); 131 | if(StringUtils.isNotBlank(alarmAggreWhiteList)){ 132 | instance.alarmAggreWhiteList = alarmAggreWhiteList; 133 | } 134 | String alarmNotifyTime = map.get(SysConstant.ALARM_NOTIFY_TIME); 135 | if(StringUtils.isNumeric(alarmNotifyTime)){ 136 | instance.alarmNotifyTime = Integer.parseInt(alarmNotifyTime); 137 | } 138 | String alarmNotifyCount = map.get(SysConstant.ALARM_NOTIFY_COUNT); 139 | if(StringUtils.isNumeric(alarmNotifyCount)){ 140 | instance.alarmNotifyCount = Integer.parseInt(alarmNotifyCount); 141 | } 142 | String alarmUriElapsedTime = map.get(SysConstant.ALARM_URI_ELAPSED_TIME); 143 | if(StringUtils.isNumeric(alarmUriElapsedTime)){ 144 | instance.alarmUriElapsedTime = Integer.parseInt(alarmUriElapsedTime); 145 | } 146 | String alarmUriElapsedCount = map.get(SysConstant.ALARM_URI_ELAPSED_COUNT); 147 | if(StringUtils.isNumeric(alarmUriElapsedCount)){ 148 | instance.alarmUriElapsedCount = Integer.parseInt(alarmUriElapsedCount); 149 | } 150 | String alarmUriElapsed = map.get(SysConstant.ALARM_URI_ELAPSED); 151 | if(JSONUtil.isJson(alarmUriElapsed)){ 152 | instance.alarmUriElapsed = alarmUriElapsed; 153 | } 154 | String alarmUriElapsedGlobal = map.get(SysConstant.ALARM_URI_ELAPSED_GLOBAL); 155 | if(StringUtils.isNumeric(alarmUriElapsedGlobal)){ 156 | instance.alarmUriElapsedGlobal = Long.parseLong(alarmUriElapsedGlobal); 157 | } 158 | 159 | String alarmTool = map.get(SysConstant.ALARM_TOOL); 160 | if(StringUtils.isNotBlank(alarmTool)){ 161 | instance.alarmTool = alarmTool; 162 | } 163 | return instance; 164 | } 165 | } 166 | -------------------------------------------------------------------------------- /common/src/main/java/com/itkevin/common/constants/SysConstant.java: -------------------------------------------------------------------------------- 1 | package com.itkevin.common.constants; 2 | 3 | /** 4 | * 常量类 5 | */ 6 | public class SysConstant { 7 | 8 | /** 9 | * 欢迎语 10 | */ 11 | public static final String WELCOME = "Welcome to the skyeye"; 12 | 13 | /** 14 | * log报警开启 15 | */ 16 | public static final String ALARM_ENABLED = "skyeye.log.alarm.enabled"; 17 | 18 | /** 19 | * 报警机器人配置(支持多个机器人) 20 | */ 21 | public static final String ALARM_DINGTALK = "skyeye.log.alarm.talk.hook"; 22 | 23 | /** 24 | * 报警严重错误机器人配置(支持多个机器人) 25 | */ 26 | public static final String ALARM_SERIOUS_DINGTALK = "skyeye.log.alarm.serious.talk.hook"; 27 | 28 | /** 29 | * 异常message输出默认长度 30 | */ 31 | public static final int ALARM_MESSAGE_STR_DEFAULT = 500; 32 | 33 | /** 34 | * 异常堆栈输出默认行数 35 | */ 36 | public static final int ALARM_STACKNUM_DEFAULT = 10; 37 | 38 | /** 39 | * 堆栈行数配置 40 | */ 41 | public static final String ALARM_STACKNUM = "skyeye.log.alarm.stackNum"; 42 | 43 | /** 44 | * 单条报警白名单 45 | */ 46 | public static final String ALARM_WHITE_LIST = "skyeye.log.alarm.white.list"; 47 | 48 | /** 49 | * 聚合报警白名单 50 | */ 51 | public static final String ALARM_AGGRE_WHITE_LIST = "skyeye.log.alarm.aggre.white.list"; 52 | 53 | /** 54 | * 报警间隔时间(单位分钟) 55 | */ 56 | public static final String ALARM_NOTIFY_TIME = "skyeye.log.alarm.notify.time"; 57 | 58 | /** 59 | * 报警次数阀值 60 | */ 61 | public static final String ALARM_NOTIFY_COUNT = "skyeye.log.alarm.notify.count"; 62 | 63 | /** 64 | * 报警间隔时间默认值(单位分钟) 65 | */ 66 | public static final Integer ALARM_NOTIFY_TIME_DEFAULT = 1; 67 | 68 | /** 69 | * 报警次数阀值默认值 70 | */ 71 | public static final Integer ALARM_NOTIFY_COUNT_DEFAULT = 5; 72 | 73 | /** 74 | * 接口耗时报警间隔时间(单位分钟) 75 | */ 76 | public static final String ALARM_URI_ELAPSED_TIME = "skyeye.log.alarm.uri.elapsed.time"; 77 | 78 | /** 79 | * 接口耗时超过阀值时间的次数阀值(阀值时间如果不指定则默认1000毫秒) 80 | */ 81 | public static final String ALARM_URI_ELAPSED_COUNT = "skyeye.log.alarm.uri.elapsed.count"; 82 | 83 | /** 84 | * 指定URI接口耗时时间阀值(单位毫秒,支持指定多个URI) 85 | */ 86 | public static final String ALARM_URI_ELAPSED = "skyeye.log.alarm.uri.elapsed"; 87 | 88 | /** 89 | * 指定接口耗时时间阀值(单位毫秒,全局指定,不配置默认1000毫秒) 90 | */ 91 | public static final String ALARM_URI_ELAPSED_GLOBAL = "skyeye.log.alarm.uri.elapsed.global"; 92 | 93 | /** 94 | * 接口耗时阀值时间默认值(单位毫秒) 95 | */ 96 | public static final long ALARM_URI_ELAPSED_DEFAULT = 1000; 97 | 98 | /** 99 | * 自定义线程池核心线程数 100 | */ 101 | public static final Integer THREAD_NUM = 5; 102 | 103 | /** 104 | * 自定义线程池最大线程数 105 | */ 106 | public static final Integer MAX_THREAD_NUM = 20; 107 | 108 | /** 109 | * 监控报警指标名称 110 | */ 111 | public static final String ALARM_METRIC_NAME = "skyeye_alarm_num_gauge"; 112 | 113 | /** 114 | * 监控报警指标名称help 115 | */ 116 | public static final String ALARM_METRIC_NAME_HELP = "skyeye异常报警数"; 117 | 118 | /** 119 | * skyeye异常报警途径 120 | */ 121 | public static final String ALARM_TOOL = "skyeye.log.alarm.tool"; 122 | 123 | /** 124 | * 默认报警途径为企业微信 125 | */ 126 | public static final String ALARM_TOOL_DEFAULT = "wework"; 127 | 128 | /** 129 | * 需要监控的日志appender 130 | */ 131 | public static final String LOG_APPENDER = "skyeye.log.appender"; 132 | 133 | } 134 | -------------------------------------------------------------------------------- /common/src/main/java/com/itkevin/common/enums/AlarmToolEnum.java: -------------------------------------------------------------------------------- 1 | package com.itkevin.common.enums; 2 | 3 | import com.itkevin.common.notice.NoticeInterface; 4 | import com.itkevin.common.notice.dingding.DingTalkNotice; 5 | import com.itkevin.common.notice.workwx.WorkWeiXinTalkNotice; 6 | 7 | import java.util.Arrays; 8 | 9 | public enum AlarmToolEnum { 10 | 11 | WEWORK("wework", WorkWeiXinTalkNotice.class), 12 | 13 | DINGDING("dingding", DingTalkNotice.class); 14 | 15 | private String code; 16 | 17 | private Class name; 18 | 19 | public String getCode() { 20 | return code; 21 | } 22 | 23 | public Class getName() { 24 | return name; 25 | } 26 | 27 | AlarmToolEnum(String code, Class name) { 28 | this.code = code; 29 | this.name = name; 30 | } 31 | 32 | public static AlarmToolEnum getByValue(String code) { 33 | return Arrays.stream(AlarmToolEnum.values()) 34 | .filter(resultCodeEnum -> resultCodeEnum.code.equals(code)) 35 | .findFirst().orElse(null); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /common/src/main/java/com/itkevin/common/enums/BusinessTypeEnum.java: -------------------------------------------------------------------------------- 1 | package com.itkevin.common.enums; 2 | 3 | /** 4 | * 业务类型枚举 5 | */ 6 | public enum BusinessTypeEnum { 7 | 8 | NOTIFY, URI_ELAPSED 9 | 10 | } 11 | -------------------------------------------------------------------------------- /common/src/main/java/com/itkevin/common/enums/LogConstantEnum.java: -------------------------------------------------------------------------------- 1 | package com.itkevin.common.enums; 2 | 3 | import java.util.Arrays; 4 | 5 | /** 6 | * 日志常量枚举 7 | */ 8 | public enum LogConstantEnum { 9 | 10 | REQUEST_URI("requestURI", "请求URI"), 11 | 12 | EXCEPTION_MESSAGE("exceptionMessage", "异常信息"), 13 | 14 | ERROR_MESSAGE("errorMessage", "error信息"), 15 | 16 | ALARM_TIME("alarmTime", "时间"), 17 | 18 | ALARM_COUNT("alarmCount", "次数"), 19 | 20 | URI_ELAPSED_THRESHOLD("uriElapsedThreshold", "阀值"), 21 | 22 | URI_ELAPSED_TRACEID_LIST("uriElapsedTraceidList", "抽样traceId"), 23 | 24 | MAX_URI_ELAPSED("maxUriElapsed", "最大耗时"), 25 | 26 | MAX_URI_ELAPSED_TRACEID("maxUriElapsedTraceId", "最大耗时traceId"), 27 | 28 | SERVER_NAME("serverName", "服务名称"), 29 | 30 | SERVER_IP("serverIP", "服务器IP"), 31 | 32 | SERVER_HOSTNAME("serverHostname", "服务器hostname"); 33 | 34 | private String code; 35 | private String name; 36 | 37 | public String getCode() { 38 | return code; 39 | } 40 | 41 | public String getName() { 42 | return name; 43 | } 44 | 45 | LogConstantEnum(String code, String name) { 46 | this.code = code; 47 | this.name = name; 48 | } 49 | 50 | public static LogConstantEnum getByValue(String code) { 51 | return Arrays.stream(LogConstantEnum.values()) 52 | .filter(resultCodeEnum -> resultCodeEnum.code.equals(code)) 53 | .findFirst().orElse(null); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /common/src/main/java/com/itkevin/common/enums/LogLevelEnum.java: -------------------------------------------------------------------------------- 1 | package com.itkevin.common.enums; 2 | 3 | public enum LogLevelEnum { 4 | 5 | NORMAL, SERIOUS 6 | } 7 | -------------------------------------------------------------------------------- /common/src/main/java/com/itkevin/common/enums/LogTypeEnum.java: -------------------------------------------------------------------------------- 1 | package com.itkevin.common.enums; 2 | 3 | /** 4 | * 日志类型枚举 5 | */ 6 | public enum LogTypeEnum { 7 | 8 | LOG4J, LOGBACK 9 | 10 | } 11 | -------------------------------------------------------------------------------- /common/src/main/java/com/itkevin/common/enums/MDCConstantEnum.java: -------------------------------------------------------------------------------- 1 | package com.itkevin.common.enums; 2 | 3 | import java.util.Arrays; 4 | 5 | /** 6 | * MDC常量枚举 7 | */ 8 | public enum MDCConstantEnum { 9 | 10 | ERROR_MESSAGE("errorMessage", "error信息"), 11 | SERVER_NAME("serverName", "服务名称"), 12 | SOURCE_IP("sourceIP", "来源IP"), 13 | SERVER_IP("serverIP", "服务器IP"), 14 | SERVER_HOSTNAME("serverHostname", "服务器hostname"), 15 | OCCURRENCE_TIME("occurrenceTime", "发生时间"), 16 | REQUEST_TYPE("requestType", "请求类型"), 17 | TRACE_ID("traceId", "跟踪traceId"), 18 | REQUEST_URI("requestURI", "请求URI"), 19 | REQUEST_PARAM("requestParam", "请求参数"), 20 | MESSAGE_TOPIC("messageTopic", "消息topic"), 21 | MESSAGE_ID("messageId", "消息msgId"), 22 | MESSAGE_KEYS("messageKeys", "消息keys"), 23 | EVENT_NAME("eventName", "事件name"), 24 | EVENT_PAYLOAD("eventPayload", "事件payload"), 25 | EXCEPTION_MESSAGE("exceptionMessage", "异常信息"), 26 | EXCEPTION_STACKTRACE("exceptionStackTrace", "异常堆栈"), 27 | DEPLOY_TAG("deployTag", "部署tag号"); 28 | 29 | private String code; 30 | private String name; 31 | 32 | public String getCode() { 33 | return code; 34 | } 35 | 36 | public String getName() { 37 | return name; 38 | } 39 | 40 | MDCConstantEnum(String code, String name) { 41 | this.code = code; 42 | this.name = name; 43 | } 44 | 45 | public static MDCConstantEnum getByValue(String code) { 46 | return Arrays.stream(MDCConstantEnum.values()) 47 | .filter(resultCodeEnum -> resultCodeEnum.code.equals(code)) 48 | .findFirst().orElse(null); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /common/src/main/java/com/itkevin/common/enums/RequestTypeEnum.java: -------------------------------------------------------------------------------- 1 | package com.itkevin.common.enums; 2 | 3 | /** 4 | * 请求类型枚举 5 | */ 6 | public enum RequestTypeEnum { 7 | 8 | HTTP, DUBBO, MQ, JOB, EVENT 9 | 10 | } 11 | -------------------------------------------------------------------------------- /common/src/main/java/com/itkevin/common/listener/ConfigListener.java: -------------------------------------------------------------------------------- 1 | package com.itkevin.common.listener; 2 | 3 | import com.ctrip.framework.apollo.ConfigChangeListener; 4 | import com.ctrip.framework.apollo.enums.PropertyChangeType; 5 | import com.ctrip.framework.apollo.model.ConfigChange; 6 | import com.ctrip.framework.apollo.model.ConfigChangeEvent; 7 | import com.google.common.collect.Maps; 8 | import com.itkevin.common.config.SysConfig; 9 | import lombok.extern.slf4j.Slf4j; 10 | import org.apache.commons.lang3.StringUtils; 11 | 12 | import java.util.HashMap; 13 | import java.util.Map; 14 | 15 | @Slf4j 16 | public class ConfigListener implements ConfigChangeListener { 17 | 18 | @Override 19 | public void onChange(ConfigChangeEvent configChangeEvent) { 20 | try { 21 | for (String key : configChangeEvent.changedKeys()) { 22 | ConfigChange change = configChangeEvent.getChange(key); 23 | PropertyChangeType changeType = change.getChangeType(); 24 | String propertyName = change.getPropertyName(); 25 | String oldValue = change.getOldValue(); 26 | String newValue = change.getNewValue(); 27 | if(StringUtils.isBlank(propertyName)){ 28 | continue; 29 | } 30 | log.info("log skyeye >>> ConfigListener changeType: {}, propertyName: {}, oldValue: {}, newValue: {}", changeType.name(), propertyName, oldValue, newValue); 31 | if(!PropertyChangeType.DELETED.equals(changeType)){ 32 | Map map = Maps.newHashMap(); 33 | map.put(propertyName,newValue); 34 | SysConfig.convertMap2SysConfig(map); 35 | } 36 | } 37 | } catch (Exception e) { 38 | log.warn("log skyeye >>> ConfigListener.onChange occur exception", e); 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /common/src/main/java/com/itkevin/common/model/BusinessData.java: -------------------------------------------------------------------------------- 1 | package com.itkevin.common.model; 2 | 3 | import lombok.Data; 4 | 5 | import java.io.Serializable; 6 | 7 | @Data 8 | public class BusinessData implements Serializable { 9 | private static final long serialVersionUID = 1L; 10 | 11 | /** 12 | * 请求URI 13 | */ 14 | private String requestURI; 15 | 16 | /** 17 | * 异常信息 18 | */ 19 | private String exceptionMessage; 20 | 21 | /** 22 | * error信息 23 | */ 24 | private String errorMessage; 25 | 26 | /** 27 | * 服务名称 28 | */ 29 | private String serverName; 30 | 31 | /** 32 | * 服务器IP 33 | */ 34 | private String serverIP; 35 | 36 | /** 37 | * 服务器hostname 38 | */ 39 | private String serverHostname; 40 | 41 | } 42 | -------------------------------------------------------------------------------- /common/src/main/java/com/itkevin/common/model/FilterMessage.java: -------------------------------------------------------------------------------- 1 | package com.itkevin.common.model; 2 | 3 | import lombok.Data; 4 | 5 | import java.io.Serializable; 6 | 7 | @Data 8 | public class FilterMessage implements Serializable { 9 | private static final long serialVersionUID = 1L; 10 | 11 | /** 12 | * 日志类型 13 | */ 14 | private String logType; 15 | 16 | /** 17 | * error信息 18 | */ 19 | private String msg; 20 | 21 | /** 22 | * 异常message 23 | */ 24 | private String message; 25 | 26 | /** 27 | * 异常堆栈 28 | */ 29 | private String stackTrace; 30 | 31 | } 32 | -------------------------------------------------------------------------------- /common/src/main/java/com/itkevin/common/model/HashedWheelData.java: -------------------------------------------------------------------------------- 1 | package com.itkevin.common.model; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Data; 5 | import lombok.NoArgsConstructor; 6 | 7 | @Data 8 | @NoArgsConstructor 9 | @AllArgsConstructor 10 | public class HashedWheelData { 11 | private static final long serialVersionUID = 1L; 12 | 13 | /** 14 | * 延迟时间(单位分钟) 15 | */ 16 | private Integer delayTime; 17 | 18 | /** 19 | * 业务类型 20 | */ 21 | private String businessType; 22 | 23 | /** 24 | * 业务id 25 | */ 26 | private String businessId; 27 | 28 | /** 29 | * 业务数据 30 | */ 31 | private String businessData; 32 | 33 | } 34 | -------------------------------------------------------------------------------- /common/src/main/java/com/itkevin/common/model/LogCompressData.java: -------------------------------------------------------------------------------- 1 | package com.itkevin.common.model; 2 | 3 | import lombok.Data; 4 | 5 | import java.io.Serializable; 6 | import java.util.List; 7 | 8 | @Data 9 | public class LogCompressData implements Serializable { 10 | private static final long serialVersionUID = 1L; 11 | 12 | /** 13 | * 请求URI 14 | */ 15 | private String requestURI; 16 | 17 | /** 18 | * 异常信息 19 | */ 20 | private String exceptionMessage; 21 | 22 | /** 23 | * error信息 24 | */ 25 | private String errorMessage; 26 | 27 | /** 28 | * 报警间隔 29 | */ 30 | private Integer alarmTime; 31 | 32 | /** 33 | * 报警次数 34 | */ 35 | private Integer alarmCount; 36 | 37 | /** 38 | * traceId集合 39 | */ 40 | private List traceIdList; 41 | 42 | /** 43 | * 接口耗时时间阀值 44 | */ 45 | private Long uriElapsedThreshold; 46 | 47 | /** 48 | * 接口最大耗时时间 49 | */ 50 | private Long maxUriElapsed; 51 | 52 | /** 53 | * 接口最大耗时traceId 54 | */ 55 | private String maxUriElapsedTraceId; 56 | 57 | /** 58 | * 服务名称 59 | */ 60 | private String serverName; 61 | 62 | /** 63 | * 服务器IP 64 | */ 65 | private String serverIP; 66 | 67 | /** 68 | * 服务器hostname 69 | */ 70 | private String serverHostname; 71 | 72 | } 73 | -------------------------------------------------------------------------------- /common/src/main/java/com/itkevin/common/model/LogData.java: -------------------------------------------------------------------------------- 1 | package com.itkevin.common.model; 2 | 3 | import lombok.Data; 4 | 5 | import java.io.Serializable; 6 | 7 | @Data 8 | public class LogData implements Serializable { 9 | private static final long serialVersionUID = 1L; 10 | 11 | /** 12 | * 是否过滤日志数据 13 | */ 14 | private Boolean filter; 15 | 16 | /** 17 | * 日志级别 18 | */ 19 | private String level; 20 | 21 | /** 22 | * error信息 23 | */ 24 | private String errorMessage; 25 | 26 | /** 27 | * 服务名称 28 | */ 29 | private String serverName; 30 | 31 | /** 32 | * 来源IP 33 | */ 34 | private String sourceIP; 35 | 36 | /** 37 | * 服务器IP 38 | */ 39 | private String serverIP; 40 | 41 | /** 42 | * 服务器hostname 43 | */ 44 | private String serverHostname; 45 | 46 | /** 47 | * 发生时间 48 | */ 49 | private String occurrenceTime; 50 | 51 | /** 52 | * 请求类型 53 | */ 54 | private String requestType; 55 | 56 | /** 57 | * 跟踪traceId 58 | */ 59 | private String traceId; 60 | 61 | /** 62 | * 请求URI 63 | */ 64 | private String requestURI; 65 | 66 | /** 67 | * 请求参数 68 | */ 69 | private String requestParam; 70 | 71 | /** 72 | * 消息topic 73 | */ 74 | private String messageTopic; 75 | 76 | /** 77 | * 消息msgId 78 | */ 79 | private String messageId; 80 | 81 | /** 82 | * 消息keys 83 | */ 84 | private String messageKeys; 85 | 86 | /** 87 | * 事件name 88 | */ 89 | private String eventName; 90 | 91 | /** 92 | * 事件payload 93 | */ 94 | private String eventPayload; 95 | 96 | /** 97 | * 异常信息 98 | */ 99 | private String exceptionMessage; 100 | 101 | /** 102 | * 异常堆栈 103 | */ 104 | private String exceptionStackTrace; 105 | 106 | } 107 | -------------------------------------------------------------------------------- /common/src/main/java/com/itkevin/common/model/LogUriElapsedData.java: -------------------------------------------------------------------------------- 1 | package com.itkevin.common.model; 2 | 3 | import lombok.Data; 4 | 5 | import java.io.Serializable; 6 | import java.util.List; 7 | 8 | @Data 9 | public class LogUriElapsedData implements Serializable { 10 | private static final long serialVersionUID = 1L; 11 | 12 | /** 13 | * 请求URI 14 | */ 15 | private String requestURI; 16 | 17 | /** 18 | * 报警间隔 19 | */ 20 | private Integer alarmTime; 21 | 22 | /** 23 | * 报警次数 24 | */ 25 | private Integer alarmCount; 26 | 27 | /** 28 | * 阀值 29 | */ 30 | private Long uriElapsedThreshold; 31 | 32 | /** 33 | * 抽样traceId 34 | */ 35 | private List traceIdList; 36 | 37 | /** 38 | * 最大耗时 39 | */ 40 | private Long maxUriElapsed; 41 | 42 | /** 43 | * 最大耗时traceId 44 | */ 45 | private String maxUriElapsedTraceId; 46 | 47 | /** 48 | * 服务名称 49 | */ 50 | private String serverName; 51 | 52 | /** 53 | * 服务器IP 54 | */ 55 | private String serverIP; 56 | 57 | /** 58 | * 服务器hostname 59 | */ 60 | private String serverHostname; 61 | } -------------------------------------------------------------------------------- /common/src/main/java/com/itkevin/common/model/TaskModel.java: -------------------------------------------------------------------------------- 1 | package com.itkevin.common.model; 2 | 3 | import lombok.Data; 4 | 5 | import java.io.Serializable; 6 | 7 | @Data 8 | public class TaskModel implements Serializable { 9 | private static final long serialVersionUID = 1L; 10 | 11 | /** 12 | * 业务id 13 | */ 14 | private String businessId; 15 | 16 | /** 17 | * 循环次数 18 | */ 19 | private Integer cycleNum; 20 | } 21 | -------------------------------------------------------------------------------- /common/src/main/java/com/itkevin/common/model/UriElapsedCollect.java: -------------------------------------------------------------------------------- 1 | package com.itkevin.common.model; 2 | 3 | import lombok.Data; 4 | 5 | import java.io.Serializable; 6 | 7 | @Data 8 | public class UriElapsedCollect implements Serializable { 9 | private static final long serialVersionUID = 1L; 10 | 11 | /** 12 | * 请求URI 13 | */ 14 | private String requestURI; 15 | 16 | /** 17 | * URI耗时 18 | */ 19 | private long elapsed; 20 | 21 | /** 22 | * 跟踪traceId 23 | */ 24 | private String traceId; 25 | 26 | /** 27 | * 服务名称 28 | */ 29 | private String serverName; 30 | 31 | /** 32 | * 服务器IP 33 | */ 34 | private String serverIP; 35 | 36 | /** 37 | * 服务器hostname 38 | */ 39 | private String serverHostname; 40 | 41 | } 42 | -------------------------------------------------------------------------------- /common/src/main/java/com/itkevin/common/model/UriElapsedData.java: -------------------------------------------------------------------------------- 1 | package com.itkevin.common.model; 2 | 3 | import lombok.Data; 4 | 5 | import java.io.Serializable; 6 | 7 | @Data 8 | public class UriElapsedData implements Serializable { 9 | private static final long serialVersionUID = 1L; 10 | 11 | /** 12 | * 请求uri 13 | */ 14 | private String uri; 15 | 16 | /** 17 | * 耗时时间(单位毫秒) 18 | */ 19 | private long elapsed; 20 | 21 | } 22 | -------------------------------------------------------------------------------- /common/src/main/java/com/itkevin/common/notice/MarkDownBaseMessage.java: -------------------------------------------------------------------------------- 1 | package com.itkevin.common.notice; 2 | 3 | import cn.hutool.json.JSONObject; 4 | import cn.hutool.json.JSONUtil; 5 | import com.itkevin.common.notice.model.BaseMessage; 6 | import lombok.Data; 7 | import lombok.EqualsAndHashCode; 8 | 9 | import java.io.Serializable; 10 | 11 | @EqualsAndHashCode(callSuper = true) 12 | @Data 13 | public class MarkDownBaseMessage extends BaseMessage implements Serializable { 14 | private static final long serialVersionUID = 1L; 15 | 16 | /** 17 | * 消息类型 18 | */ 19 | private String msgType = "markdown"; 20 | 21 | /** 22 | * 消息标题 23 | */ 24 | private String title; 25 | 26 | /** 27 | * 消息内容 28 | */ 29 | private String content; 30 | 31 | @Override 32 | public String toString() { 33 | JSONObject markdownContent = new JSONObject(); 34 | markdownContent.put("title", this.getTitle()); 35 | markdownContent.put("content", this.getContent()); 36 | JSONObject json = new JSONObject(); 37 | json.put("msgtype", this.getMsgType()); 38 | json.put("at", this.setAtAllAndMobile(this.getAtMobiles())); 39 | json.put("markdown", markdownContent); 40 | return JSONUtil.toJsonStr(json); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /common/src/main/java/com/itkevin/common/notice/NoticeInterface.java: -------------------------------------------------------------------------------- 1 | package com.itkevin.common.notice; 2 | 3 | import okhttp3.OkHttpClient; 4 | 5 | import java.util.concurrent.TimeUnit; 6 | 7 | /** 8 | * 开放通知接口 9 | */ 10 | public interface NoticeInterface { 11 | 12 | /** 13 | * 发送消息 14 | * @param baseMessage 15 | */ 16 | void sendMessage(MarkDownBaseMessage baseMessage); 17 | 18 | /** 19 | * 配置过滤器 20 | * @return 21 | */ 22 | String filterFlag(); 23 | 24 | /** 25 | * 初始化OkHttpClient实例 26 | * @return 27 | */ 28 | static OkHttpClient initOkHttpClient() { 29 | return new OkHttpClient.Builder() 30 | .retryOnConnectionFailure(true) 31 | .connectTimeout(500, TimeUnit.MILLISECONDS) 32 | .readTimeout(500, TimeUnit.MILLISECONDS) 33 | .writeTimeout(500, TimeUnit.MILLISECONDS) 34 | .build(); 35 | } 36 | 37 | } 38 | -------------------------------------------------------------------------------- /common/src/main/java/com/itkevin/common/notice/dingding/DingConfigData.java: -------------------------------------------------------------------------------- 1 | package com.itkevin.common.notice.dingding; 2 | 3 | import lombok.Data; 4 | 5 | import java.io.Serializable; 6 | 7 | @Data 8 | public class DingConfigData implements Serializable { 9 | private static final long serialVersionUID = 1L; 10 | 11 | /** 12 | * webHook 13 | */ 14 | private String webHook; 15 | 16 | /** 17 | * token 18 | */ 19 | private String accessToken; 20 | 21 | /** 22 | * secret 23 | */ 24 | private String secret; 25 | 26 | } 27 | -------------------------------------------------------------------------------- /common/src/main/java/com/itkevin/common/notice/dingding/DingMarkDownMessage.java: -------------------------------------------------------------------------------- 1 | package com.itkevin.common.notice.dingding; 2 | 3 | import cn.hutool.json.JSONObject; 4 | import cn.hutool.json.JSONUtil; 5 | import com.itkevin.common.notice.MarkDownBaseMessage; 6 | import com.itkevin.common.notice.model.BaseMessage; 7 | import lombok.Data; 8 | import lombok.EqualsAndHashCode; 9 | 10 | import java.io.Serializable; 11 | 12 | @EqualsAndHashCode(callSuper = true) 13 | @Data 14 | public class DingMarkDownMessage extends MarkDownBaseMessage implements Serializable { 15 | private static final long serialVersionUID = 1L; 16 | 17 | /** 18 | * 消息类型 19 | */ 20 | private String msgType = "markdown"; 21 | 22 | /** 23 | * 消息标题 24 | */ 25 | private String title; 26 | 27 | /** 28 | * 消息内容 29 | */ 30 | private String content; 31 | 32 | @Override 33 | public String toString() { 34 | JSONObject markdownContent = new JSONObject(); 35 | markdownContent.put("title", this.getTitle()); 36 | markdownContent.put("text", this.getContent()); 37 | JSONObject json = new JSONObject(); 38 | json.put("msgtype", this.getMsgType()); 39 | json.put("at", this.setAtAllAndMobile(this.getAtMobiles())); 40 | json.put("markdown", markdownContent); 41 | return JSONUtil.toJsonStr(json); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /common/src/main/java/com/itkevin/common/notice/dingding/DingTalkNotice.java: -------------------------------------------------------------------------------- 1 | package com.itkevin.common.notice.dingding; 2 | 3 | import cn.hutool.core.util.CharsetUtil; 4 | import cn.hutool.http.HttpUtil; 5 | import cn.hutool.json.JSONObject; 6 | import cn.hutool.json.JSONUtil; 7 | import com.itkevin.common.config.SysConfig; 8 | import com.itkevin.common.enums.LogLevelEnum; 9 | import com.itkevin.common.notice.MarkDownBaseMessage; 10 | import com.itkevin.common.notice.NoticeInterface; 11 | import com.itkevin.common.util.LocalCacheUtils; 12 | import com.itkevin.common.util.StringConverterFactory; 13 | import com.jakewharton.retrofit2.adapter.reactor.ReactorCallAdapterFactory; 14 | import lombok.extern.slf4j.Slf4j; 15 | import okhttp3.MediaType; 16 | import okhttp3.RequestBody; 17 | import okhttp3.ResponseBody; 18 | import org.apache.commons.lang3.StringUtils; 19 | import reactor.core.publisher.Mono; 20 | import retrofit2.Response; 21 | import retrofit2.Retrofit; 22 | import retrofit2.converter.gson.GsonConverterFactory; 23 | 24 | import javax.crypto.Mac; 25 | import javax.crypto.spec.SecretKeySpec; 26 | import java.net.URLEncoder; 27 | import java.util.Base64; 28 | import java.util.List; 29 | import java.util.Map; 30 | 31 | @Slf4j 32 | public class DingTalkNotice implements NoticeInterface { 33 | 34 | private static volatile DingTalkNotice dingTalkNotice; 35 | 36 | public static DingTalkNotice getInstance() { 37 | try { 38 | if (null == dingTalkNotice) { 39 | synchronized (DingTalkNotice.class) { 40 | if (null == dingTalkNotice) { 41 | dingTalkNotice = new DingTalkNotice(); 42 | } 43 | } 44 | } 45 | } catch (Exception e) { 46 | e.printStackTrace(); 47 | } 48 | return dingTalkNotice; 49 | } 50 | 51 | /** 52 | * 钉钉服务接口(retrofit2-reactor-adapter:https://github.com/JakeWharton/retrofit2-reactor-adapter) 53 | */ 54 | private static DingTalkService dingTalkService = new Retrofit.Builder() 55 | .baseUrl("https://oapi.dingtalk.com") 56 | .client(NoticeInterface.initOkHttpClient()) 57 | .addConverterFactory(new StringConverterFactory()) 58 | .addConverterFactory(GsonConverterFactory.create()) 59 | .addCallAdapterFactory(ReactorCallAdapterFactory.create()) 60 | .build() 61 | .create(DingTalkService.class); 62 | 63 | /** 64 | * 钉钉机器人标识 65 | */ 66 | private static final String WEBHOOK_INDEX = "DINGDING_WEBHOOK_INDEX"; 67 | 68 | /** 69 | * 发送钉钉消息 70 | * 71 | * @param dingMarkDownMessage 72 | */ 73 | @Override 74 | public void sendMessage(MarkDownBaseMessage dingMarkDownMessage) { 75 | try { 76 | String level = dingMarkDownMessage.getLevel(); 77 | String alarmDingTalk = StringUtils.isNotBlank(level) && level.equals(LogLevelEnum.SERIOUS.name()) 78 | ? SysConfig.instance.getAlarmSeriousTalkHook() 79 | : SysConfig.instance.getAlarmTalkHook(); 80 | if (StringUtils.isBlank(alarmDingTalk)) { 81 | log.warn("log skyeye >>> config 'skyeye.log.alarm.dingtalk' or 'skyeye.log.alarm.serious.dingtalk' is null"); 82 | return; 83 | } 84 | List dingConfigDataList = JSONUtil.toList(JSONUtil.parseArray(alarmDingTalk), DingConfigData.class); 85 | String str = converMarkDownDingMessage2Str(dingMarkDownMessage); 86 | send(dingConfigDataList, str); 87 | } catch (Exception e) { 88 | log.warn("log skyeye >>> DingTalkUtils.sendMessage occur exception", e); 89 | } 90 | } 91 | 92 | private String converMarkDownDingMessage2Str(MarkDownBaseMessage markDownBaseMessage) { 93 | JSONObject markdownContent = new JSONObject(); 94 | markdownContent.put("title", markDownBaseMessage.getTitle()); 95 | markdownContent.put("text", markDownBaseMessage.getContent()); 96 | JSONObject json = new JSONObject(); 97 | json.put("msgtype", markDownBaseMessage.getMsgType()); 98 | json.put("at", markDownBaseMessage.setAtAllAndMobile(markDownBaseMessage.getAtMobiles())); 99 | json.put("markdown", markdownContent); 100 | return JSONUtil.toJsonStr(json); 101 | } 102 | 103 | /** 104 | * 发送 105 | * 106 | * @param dingConfigDataList 107 | * @param jsonContent 108 | */ 109 | private void send(List dingConfigDataList, String jsonContent) { 110 | int currentIndex = getIndex(dingConfigDataList.size()); 111 | DingConfigData dingConfigData = dingConfigDataList.get(currentIndex); 112 | String accessToken = dingConfigData.getAccessToken(); 113 | if (StringUtils.isBlank(accessToken)) { 114 | Map paramMap = HttpUtil.decodeParamMap(dingConfigData.getWebHook(), CharsetUtil.defaultCharset()); 115 | accessToken = paramMap.get("access_token"); 116 | accessToken = StringUtils.isBlank(accessToken) ? paramMap.get("accessToken") : accessToken; 117 | } 118 | send(accessToken, dingConfigData.getSecret(), jsonContent); 119 | } 120 | 121 | /** 122 | * 发送 123 | * 124 | * @param accessToken 125 | * @param secret 126 | * @param jsonContent 127 | */ 128 | private boolean send(String accessToken, String secret, String jsonContent) { 129 | try { 130 | Long timestamp = System.currentTimeMillis(); 131 | String sign = StringUtils.isNotBlank(secret) ? signData(timestamp, secret) : ""; 132 | RequestBody requestBody = RequestBody.create(MediaType.parse("application/json"), jsonContent); 133 | log.info("log skyeye >>> send dingding accessToken: {}, timestamp: {}, sign: {}", accessToken, timestamp, sign); 134 | Mono> mono = dingTalkService.robotSendMono(accessToken, timestamp, sign, requestBody); 135 | ResponseBody responseBody = mono.blockOptional().map(Response::body).orElse(null); 136 | String string = responseBody != null ? responseBody.string() : ""; 137 | log.info("log skyeye >>> send dingding result: {}", string); 138 | if (string.contains("")) { 139 | return false; 140 | } 141 | JSONObject result = JSONUtil.parseObj(string); 142 | if (result.get("errcode") == null || !"0".equals(String.valueOf(result.get("errcode")))) { 143 | return false; 144 | } 145 | return true; 146 | } catch (Exception e) { 147 | log.warn("log skyeye >>> DingTalkUtils.send occur exception", e); 148 | return false; 149 | } 150 | } 151 | 152 | /** 153 | * 验签 154 | * 155 | * @param timestamp 156 | * @param secret 157 | * @return 158 | */ 159 | private String signData(Long timestamp, String secret) { 160 | try { 161 | String stringToSign = timestamp + "\n" + secret; 162 | Mac mac = Mac.getInstance("HmacSHA256"); 163 | mac.init(new SecretKeySpec(secret.getBytes("UTF-8"), "HmacSHA256")); 164 | byte[] signData = mac.doFinal(stringToSign.getBytes("UTF-8")); 165 | return URLEncoder.encode(new String(Base64.getEncoder().encode(signData)), "UTF-8"); 166 | } catch (Exception e) { 167 | log.warn("log skyeye >>> DingTalkUtils.signData occur exception", e); 168 | return null; 169 | } 170 | } 171 | 172 | 173 | /** 174 | * 获取第几个机器人 175 | * 176 | * @param totalCount 177 | * @return 178 | */ 179 | private synchronized int getIndex(int totalCount) { 180 | int currentIndex = LocalCacheUtils.getIntCache(WEBHOOK_INDEX); 181 | if (currentIndex > totalCount - 1) { 182 | currentIndex = 0; 183 | LocalCacheUtils.putIntCache(WEBHOOK_INDEX, currentIndex); 184 | } 185 | LocalCacheUtils.incr(WEBHOOK_INDEX); 186 | return currentIndex; 187 | } 188 | 189 | public static void main(String[] args) { 190 | // List dingConfigDataList = new ArrayList<>(); 191 | // DingConfigData dingConfigData1= new DingConfigData(); 192 | // dingConfigData1.setWebHook("https://oapi.dingtalk.com/robot/send?access_token=xxxx"); 193 | // dingConfigData1.setSecret("SEC2e6249a1bf419db8a89643ebf2625dbc0c3e47af12ce130a6582415d3f38da05"); 194 | // DingConfigData dingConfigData2= new DingConfigData(); 195 | // dingConfigData2.setWebHook("https://oapi.dingtalk.com/robot/send?access_token=xxxx"); 196 | // dingConfigData2.setSecret("SECc193b8c3b7e47ad0fd4821bf26f08dc777010ecef372689604922b1c78e8184c"); 197 | // dingConfigDataList.add(dingConfigData1); 198 | // dingConfigDataList.add(dingConfigData2); 199 | 200 | // DingMarkDownMessage message = new DingMarkDownMessage(); 201 | // message.setTitle("出错啦!"); 202 | // String exceptionMessage; 203 | // String exceptionStackTrace; 204 | // try { 205 | // throw new RuntimeException("异常message"); 206 | // } catch (Exception e) { 207 | // exceptionMessage = e.getMessage(); 208 | // exceptionStackTrace = ExceptionUtils.getStackTrace(e); 209 | // } 210 | // String content = "## **" + MDCConstantEnum.ERROR_MESSAGE.getName() + ":" + "出错啦!" + "**" + System.getProperty("line.separator") + 211 | // "+ " + MDCConstantEnum.SERVER_NAME.getName() + ":" + "localhost" + System.getProperty("line.separator") + 212 | // "+ " + MDCConstantEnum.SOURCE_IP.getName() + ":" + "10.155.8.91" + System.getProperty("line.separator") + 213 | // "+ " + MDCConstantEnum.SERVER_IP.getName() + ":" + "10.155.8.91" + System.getProperty("line.separator") + 214 | // "+ " + MDCConstantEnum.SERVER_HOSTNAME.getName() + ":" + "itkevin.it.com" + System.getProperty("line.separator") + 215 | // "+ " + MDCConstantEnum.OCCURRENCE_TIME.getName() + ":" + "2020-06-23 14:20:05" + System.getProperty("line.separator") + 216 | // "+ " + MDCConstantEnum.REQUEST_TYPE.getName() + ":" + "http GET" + System.getProperty("line.separator") + 217 | // "+ " + MDCConstantEnum.TRACE_ID.getName() + ":" + "4696.167.15928932046440001" + System.getProperty("line.separator") + 218 | // "+ " + MDCConstantEnum.REQUEST_URI.getName() + ":" + "/api/health" + System.getProperty("line.separator") + 219 | // "+ " + MDCConstantEnum.REQUEST_PARAM.getName() + ":" + "{a=a, b=b}" + System.getProperty("line.separator") + 220 | // "+ " + MDCConstantEnum.EXCEPTION_MESSAGE.getName() + ":" + exceptionMessage + System.getProperty("line.separator") + 221 | // "+ " + MDCConstantEnum.EXCEPTION_STACKTRACE.getName() + ":" + System.getProperty("line.separator") + System.getProperty("line.separator") + 222 | // "`" + exceptionStackTrace + "`"; 223 | // message.setContent(content); 224 | // DingTalkNotice.sendMessage(message); 225 | } 226 | 227 | @Override 228 | public String filterFlag() { 229 | return "dingding"; 230 | } 231 | } 232 | -------------------------------------------------------------------------------- /common/src/main/java/com/itkevin/common/notice/dingding/DingTalkService.java: -------------------------------------------------------------------------------- 1 | package com.itkevin.common.notice.dingding; 2 | 3 | import okhttp3.RequestBody; 4 | import okhttp3.ResponseBody; 5 | import reactor.core.publisher.Mono; 6 | import retrofit2.Call; 7 | import retrofit2.Response; 8 | import retrofit2.http.Body; 9 | import retrofit2.http.Headers; 10 | import retrofit2.http.POST; 11 | import retrofit2.http.Query; 12 | 13 | /** 14 | * 钉钉服务接口 15 | */ 16 | public interface DingTalkService { 17 | 18 | /** 19 | * 发送钉钉消息 20 | * @param accessToken 21 | * @param timestamp 22 | * @param sign 23 | * @param body 24 | * @return 25 | */ 26 | @Headers({"Content-Type: application/json; charset=utf-8"}) 27 | @POST("/robot/send") 28 | Call robotSendCall(@Query("access_token") String accessToken, @Query("timestamp") Long timestamp, @Query("sign") String sign, @Body RequestBody body); 29 | 30 | /** 31 | * 发送钉钉消息 32 | * @param accessToken 33 | * @param timestamp 34 | * @param sign 35 | * @param body 36 | * @return 37 | */ 38 | @Headers({"Content-Type: application/json; charset=utf-8"}) 39 | @POST("/robot/send") 40 | Mono> robotSendMonoStr(@Query("access_token") String accessToken, @Query("timestamp") Long timestamp, @Query("sign") String sign, @Body RequestBody body); 41 | 42 | /** 43 | * 发送钉钉消息 44 | * @param accessToken 45 | * @param timestamp 46 | * @param sign 47 | * @param body 48 | * @return 49 | */ 50 | @Headers({"Content-Type: application/json; charset=utf-8"}) 51 | @POST("/robot/send") 52 | Mono> robotSendMono(@Query("access_token") String accessToken, @Query("timestamp") Long timestamp, @Query("sign") String sign, @Body RequestBody body); 53 | 54 | } 55 | -------------------------------------------------------------------------------- /common/src/main/java/com/itkevin/common/notice/dingding/DingTextMessage.java: -------------------------------------------------------------------------------- 1 | package com.itkevin.common.notice.dingding; 2 | 3 | import cn.hutool.json.JSONObject; 4 | import cn.hutool.json.JSONUtil; 5 | import com.itkevin.common.notice.model.BaseMessage; 6 | import lombok.Data; 7 | import lombok.EqualsAndHashCode; 8 | 9 | import java.io.Serializable; 10 | 11 | @EqualsAndHashCode(callSuper = true) 12 | @Data 13 | public class DingTextMessage extends BaseMessage implements Serializable { 14 | private static final long serialVersionUID = 1L; 15 | 16 | /** 17 | * 消息类型 18 | */ 19 | private String msgType = "text"; 20 | 21 | /** 22 | * 消息内容 23 | */ 24 | private String content; 25 | 26 | @Override 27 | public String toString() { 28 | JSONObject content = new JSONObject(); 29 | content.put("content", this.getContent()); 30 | JSONObject json = new JSONObject(); 31 | json.put("msgtype", this.getMsgType()); 32 | json.put("at", this.setAtAllAndMobile(this.getAtMobiles())); 33 | json.put("text", content); 34 | return JSONUtil.toJsonStr(json); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /common/src/main/java/com/itkevin/common/notice/model/BaseMessage.java: -------------------------------------------------------------------------------- 1 | package com.itkevin.common.notice.model; 2 | 3 | import cn.hutool.core.collection.CollectionUtil; 4 | import cn.hutool.json.JSONObject; 5 | import com.itkevin.common.enums.LogLevelEnum; 6 | import lombok.Data; 7 | 8 | import java.io.Serializable; 9 | import java.util.List; 10 | 11 | @Data 12 | public class BaseMessage implements Serializable { 13 | 14 | private static final long serialVersionUID = 1L; 15 | 16 | /** 17 | * {@link LogLevelEnum} 18 | * 消息级别 19 | */ 20 | private String level; 21 | 22 | /** 23 | * at所有人 24 | */ 25 | private Boolean isAtAll; 26 | 27 | /** 28 | * at成员 29 | */ 30 | private List atMobiles; 31 | 32 | /** 33 | * at成员 34 | * @param atMobiles 手机号 35 | * @return 36 | */ 37 | public JSONObject setAtAllAndMobile(List atMobiles) { 38 | JSONObject atMobile = new JSONObject(); 39 | if (!CollectionUtil.isEmpty(atMobiles)) { 40 | atMobile.put("atMobiles", atMobiles); 41 | atMobile.put("isAtAll", false); 42 | } 43 | return atMobile; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /common/src/main/java/com/itkevin/common/notice/workwx/WeWorkConfigData.java: -------------------------------------------------------------------------------- 1 | package com.itkevin.common.notice.workwx; 2 | 3 | import lombok.Data; 4 | 5 | import java.io.Serializable; 6 | 7 | @Data 8 | public class WeWorkConfigData implements Serializable { 9 | private static final long serialVersionUID = 1L; 10 | 11 | /** 12 | * webHook 13 | */ 14 | private String webHook; 15 | 16 | /** 17 | * key 18 | */ 19 | private String key; 20 | 21 | 22 | } 23 | -------------------------------------------------------------------------------- /common/src/main/java/com/itkevin/common/notice/workwx/WeWorkMarkDownMessage.java: -------------------------------------------------------------------------------- 1 | package com.itkevin.common.notice.workwx; 2 | 3 | import cn.hutool.json.JSONObject; 4 | import cn.hutool.json.JSONUtil; 5 | import com.itkevin.common.notice.MarkDownBaseMessage; 6 | import lombok.Data; 7 | import lombok.EqualsAndHashCode; 8 | 9 | import java.io.Serializable; 10 | 11 | @EqualsAndHashCode(callSuper = true) 12 | @Data 13 | public class WeWorkMarkDownMessage extends MarkDownBaseMessage implements Serializable { 14 | private static final long serialVersionUID = 1L; 15 | 16 | /** 17 | * 消息类型 18 | */ 19 | private String msgType = "markdown"; 20 | 21 | /** 22 | * 消息标题 23 | */ 24 | private String title; 25 | 26 | /** 27 | * 消息内容 28 | */ 29 | private String content; 30 | 31 | @Override 32 | public String toString() { 33 | JSONObject markdownContent = new JSONObject(); 34 | markdownContent.put("title", this.getTitle()); 35 | markdownContent.put("content", this.getContent()); 36 | markdownContent.put("mentioned_list", this.getAtMobiles()); 37 | JSONObject json = new JSONObject(); 38 | json.put("msgtype", this.getMsgType()); 39 | json.put("markdown", markdownContent); 40 | return JSONUtil.toJsonStr(json); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /common/src/main/java/com/itkevin/common/notice/workwx/WeWorkTextMessage.java: -------------------------------------------------------------------------------- 1 | package com.itkevin.common.notice.workwx; 2 | 3 | import cn.hutool.json.JSONObject; 4 | import cn.hutool.json.JSONUtil; 5 | import com.itkevin.common.notice.model.BaseMessage; 6 | import lombok.Data; 7 | import lombok.EqualsAndHashCode; 8 | 9 | import java.io.Serializable; 10 | 11 | @EqualsAndHashCode(callSuper = true) 12 | @Data 13 | public class WeWorkTextMessage extends BaseMessage implements Serializable { 14 | private static final long serialVersionUID = 1L; 15 | 16 | /** 17 | * 消息类型 18 | */ 19 | private String msgType = "text"; 20 | 21 | /** 22 | * 消息内容 23 | */ 24 | private String content; 25 | 26 | @Override 27 | public String toString() { 28 | JSONObject content = new JSONObject(); 29 | content.put("content", this.getContent()); 30 | JSONObject json = new JSONObject(); 31 | json.put("msgtype", this.getMsgType()); 32 | json.put("at", this.setAtAllAndMobile(this.getAtMobiles())); 33 | json.put("text", content); 34 | return JSONUtil.toJsonStr(json); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /common/src/main/java/com/itkevin/common/notice/workwx/WorkWeiXinTalkNotice.java: -------------------------------------------------------------------------------- 1 | package com.itkevin.common.notice.workwx; 2 | 3 | import cn.hutool.core.util.CharsetUtil; 4 | import cn.hutool.http.HttpUtil; 5 | import cn.hutool.json.JSONObject; 6 | import cn.hutool.json.JSONUtil; 7 | import com.itkevin.common.config.SysConfig; 8 | import com.itkevin.common.enums.LogLevelEnum; 9 | import com.itkevin.common.notice.MarkDownBaseMessage; 10 | import com.itkevin.common.notice.NoticeInterface; 11 | import com.itkevin.common.util.LocalCacheUtils; 12 | import com.itkevin.common.util.StringConverterFactory; 13 | import com.jakewharton.retrofit2.adapter.reactor.ReactorCallAdapterFactory; 14 | import lombok.extern.slf4j.Slf4j; 15 | import okhttp3.MediaType; 16 | import okhttp3.RequestBody; 17 | import okhttp3.ResponseBody; 18 | import org.apache.commons.lang3.StringUtils; 19 | import reactor.core.publisher.Mono; 20 | import retrofit2.Response; 21 | import retrofit2.Retrofit; 22 | import retrofit2.converter.gson.GsonConverterFactory; 23 | 24 | import java.util.List; 25 | import java.util.Map; 26 | 27 | @Slf4j 28 | public class WorkWeiXinTalkNotice implements NoticeInterface { 29 | /** 30 | * 机器人标识 31 | */ 32 | private static final String WEBHOOK_INDEX = "WEWORK_WEBHOOK_INDEX"; 33 | 34 | /** 35 | * baseUrl 36 | */ 37 | private static final String BASE_URL = "https://qyapi.weixin.qq.com"; 38 | 39 | private static volatile WorkWeiXinTalkNotice workWeiXinTalkNotice; 40 | 41 | public static WorkWeiXinTalkNotice getInstance(){ 42 | try { 43 | if(null == workWeiXinTalkNotice){ 44 | synchronized (WorkWeiXinTalkNotice.class){ 45 | if(null == workWeiXinTalkNotice){ 46 | workWeiXinTalkNotice = new WorkWeiXinTalkNotice(); 47 | } 48 | } 49 | } 50 | }catch (Exception e){ 51 | e.printStackTrace(); 52 | } 53 | return workWeiXinTalkNotice; 54 | } 55 | 56 | /** 57 | * 服务接口(retrofit2-reactor-adapter:https://github.com/JakeWharton/retrofit2-reactor-adapter) 58 | */ 59 | private static WorkWeiXinTalkService workWeiXinTalkService = new Retrofit.Builder() 60 | .baseUrl(BASE_URL) 61 | .client(NoticeInterface.initOkHttpClient()) 62 | .addConverterFactory(new StringConverterFactory()) 63 | .addConverterFactory(GsonConverterFactory.create()) 64 | .addCallAdapterFactory(ReactorCallAdapterFactory.create()) 65 | .build() 66 | .create(WorkWeiXinTalkService.class); 67 | 68 | /** 69 | * 发送消息 70 | * @param markDownBaseMessage 71 | */ 72 | @Override 73 | public void sendMessage(MarkDownBaseMessage markDownBaseMessage) { 74 | try { 75 | String level = markDownBaseMessage.getLevel(); 76 | String alarmDingTalk = StringUtils.isNotBlank(level) && level.equals(LogLevelEnum.SERIOUS.name()) 77 | ? SysConfig.instance.getAlarmSeriousTalkHook() 78 | : SysConfig.instance.getAlarmTalkHook(); 79 | if (StringUtils.isBlank(alarmDingTalk)) { 80 | log.warn("log skyeye >>> config 'skyeye.log.alarm.weWorktalk' or 'skyeye.log.alarm.serious.weWorktalk' is null"); 81 | return; 82 | } 83 | List weWorkConfigDataList = JSONUtil.toList(JSONUtil.parseArray(alarmDingTalk), WeWorkConfigData.class); 84 | String str = converMarkDownBaseMessage2Str(markDownBaseMessage); 85 | send(weWorkConfigDataList, str); 86 | } catch (Exception e) { 87 | log.warn("log skyeye >>> WeWorkTalkUtils.sendMessage occur exception", e); 88 | } 89 | } 90 | 91 | private String converMarkDownBaseMessage2Str(MarkDownBaseMessage markDownBaseMessage) { 92 | JSONObject markdownContent = new JSONObject(); 93 | markdownContent.put("title", markDownBaseMessage.getTitle()); 94 | markdownContent.put("content", markDownBaseMessage.getContent()); 95 | markdownContent.put("mentioned_list", markDownBaseMessage.getAtMobiles()); 96 | JSONObject json = new JSONObject(); 97 | json.put("msgtype", markDownBaseMessage.getMsgType()); 98 | json.put("markdown", markdownContent); 99 | return JSONUtil.toJsonStr(json); 100 | } 101 | 102 | 103 | /** 104 | * 发送 105 | * @param weWorkConfigDataList 106 | * @param jsonContent 107 | */ 108 | private void send(List weWorkConfigDataList, String jsonContent) { 109 | int currentIndex = getIndex(weWorkConfigDataList.size()); 110 | WeWorkConfigData weWorkConfigData = weWorkConfigDataList.get(currentIndex); 111 | String key = weWorkConfigData.getKey(); 112 | if (StringUtils.isBlank(key)) { 113 | Map paramMap = HttpUtil.decodeParamMap(weWorkConfigData.getWebHook(), CharsetUtil.defaultCharset()); 114 | key = paramMap.get("key"); 115 | } 116 | send(key, jsonContent); 117 | } 118 | 119 | /** 120 | * 发送 121 | * @param key 122 | * @param jsonContent 123 | */ 124 | private boolean send(String key, String jsonContent) { 125 | try { 126 | RequestBody requestBody = RequestBody.create(MediaType.parse("application/json"), jsonContent); 127 | log.info("log skyeye >>> send weWorktalk key: {}", key); 128 | Mono> mono = workWeiXinTalkService.robotSendMono(key, requestBody); 129 | ResponseBody responseBody = mono.blockOptional().map(Response::body).orElse(null); 130 | String string = responseBody != null ? responseBody.string() : ""; 131 | log.info("log skyeye >>> send weWorktalk result: {}", string); 132 | if (string.contains("")) { 133 | return false; 134 | } 135 | JSONObject result = JSONUtil.parseObj(string); 136 | if (result.get("errcode") == null || !"0".equals(String.valueOf(result.get("errcode")))) { 137 | return false; 138 | } 139 | return true; 140 | } catch (Exception e) { 141 | log.warn("log skyeye >>> weWorkTalkUtils.send occur exception", e); 142 | return false; 143 | } 144 | } 145 | 146 | /** 147 | * 获取第几个机器人 148 | * @param totalCount 149 | * @return 150 | */ 151 | private synchronized int getIndex(int totalCount) { 152 | int currentIndex = LocalCacheUtils.getIntCache(WEBHOOK_INDEX); 153 | if (currentIndex > totalCount - 1) { 154 | currentIndex = 0; 155 | LocalCacheUtils.putIntCache(WEBHOOK_INDEX, currentIndex); 156 | } 157 | LocalCacheUtils.incr(WEBHOOK_INDEX); 158 | return currentIndex; 159 | } 160 | 161 | @Override 162 | public String filterFlag() { 163 | return "wework"; 164 | } 165 | } 166 | -------------------------------------------------------------------------------- /common/src/main/java/com/itkevin/common/notice/workwx/WorkWeiXinTalkService.java: -------------------------------------------------------------------------------- 1 | package com.itkevin.common.notice.workwx; 2 | 3 | import com.itkevin.common.notice.NoticeInterface; 4 | import okhttp3.RequestBody; 5 | import okhttp3.ResponseBody; 6 | import reactor.core.publisher.Mono; 7 | import retrofit2.Call; 8 | import retrofit2.Response; 9 | import retrofit2.http.Body; 10 | import retrofit2.http.Headers; 11 | import retrofit2.http.POST; 12 | import retrofit2.http.Query; 13 | 14 | /** 15 | * 企业微信服务接口 16 | */ 17 | public interface WorkWeiXinTalkService { 18 | 19 | 20 | /** 21 | * 发送企业微信消息 22 | * 23 | * @param key 24 | * @param body 25 | * @return 26 | */ 27 | @Headers({"Content-Type: application/json; charset=utf-8"}) 28 | @POST("/cgi-bin/webhook/send") 29 | Call robotSendCall(@Query("key") String key, @Body RequestBody body); 30 | 31 | /** 32 | * 发送企业微信消息 33 | * 34 | * @param key 35 | * @param body 36 | * @return 37 | */ 38 | @Headers({"Content-Type: application/json; charset=utf-8"}) 39 | @POST("/cgi-bin/webhook/send") 40 | Mono> robotSendMonoStr(@Query("key") String key, @Body RequestBody body); 41 | 42 | /** 43 | * 发送企业微信消息 44 | * 45 | * @param key 46 | * @param body 47 | * @return 48 | */ 49 | @Headers({"Content-Type: application/json; charset=utf-8"}) 50 | @POST("/cgi-bin/webhook/send") 51 | Mono> robotSendMono(@Query("key") String key, @Body RequestBody body); 52 | 53 | } 54 | -------------------------------------------------------------------------------- /common/src/main/java/com/itkevin/common/util/BatchConsumerCallbackAdvisor.java: -------------------------------------------------------------------------------- 1 | //package com.itkevin.common.util; 2 | // 3 | //import com.ctrip.framework.foundation.Foundation; 4 | //import com.google.common.collect.Lists; 5 | //import com.itkevin.common.enums.MDCConstantEnum; 6 | //import com.itkevin.common.enums.RequestTypeEnum; 7 | //import lombok.extern.slf4j.Slf4j; 8 | //import net.bytebuddy.asm.Advice; 9 | //import org.apache.rocketmq.common.message.MessageExt; 10 | //import org.apache.skywalking.apm.toolkit.trace.TraceContext; 11 | // 12 | //import java.lang.reflect.Method; 13 | //import java.util.List; 14 | //import java.util.stream.Collectors; 15 | // 16 | //@Slf4j 17 | //public class BatchConsumerCallbackAdvisor { 18 | // 19 | // @Advice.OnMethodEnter 20 | // public static void onMethodEnter(@Advice.This Object object, @Advice.Origin Method method, @Advice.AllArguments Object[] arguments) { 21 | // try { 22 | // @SuppressWarnings("unchecked") 23 | // List batchMessage = arguments != null && arguments.length > 0 ? (List) arguments[0] : null; 24 | // if (!CollectionUtil.isEmpty(batchMessage)) { 25 | // List topicList = Lists.newArrayList(); 26 | // List msgIdList = Lists.newArrayList(); 27 | // List keysList = Lists.newArrayList(); 28 | // for (BatchConsumerCallback.MQMessage mqMessage : batchMessage) { 29 | // MessageExt messageExt = (MessageExt) mqMessage.getMessageExt(); 30 | // topicList.add(messageExt.getTopic()); 31 | // msgIdList.add(messageExt.getMsgId()); 32 | // keysList.add(messageExt.getKeys()); 33 | // } 34 | // MDCUtils.put(MDCConstantEnum.SERVER_NAME.getCode(), Foundation.app().getAppId()); 35 | // MDCUtils.put(MDCConstantEnum.SERVER_HOSTNAME.getCode(), IPUtils.getLocalHostName()); 36 | // MDCUtils.put(MDCConstantEnum.SERVER_IP.getCode(), IPUtils.getLocalIp()); 37 | // MDCUtils.put(MDCConstantEnum.REQUEST_TYPE.getCode(), RequestTypeEnum.MQ.name().toLowerCase()); 38 | // MDCUtils.put(MDCConstantEnum.TRACE_ID.getCode(), TraceContext.traceId()); 39 | // MDCUtils.put(MDCConstantEnum.REQUEST_URI.getCode(), object.getClass().getName() + "#call"); 40 | // MDCUtils.put(MDCConstantEnum.MESSAGE_TOPIC.getCode(), topicList.stream().distinct().collect(Collectors.joining(","))); 41 | // MDCUtils.put(MDCConstantEnum.MESSAGE_ID.getCode(), String.join(",", msgIdList)); 42 | // MDCUtils.put(MDCConstantEnum.MESSAGE_KEYS.getCode(), String.join(",", keysList)); 43 | // } 44 | // } catch (Exception e) { 45 | // log.warn("log skyeye >>> BatchConsumerCallbackAdvisor.onMethodEnter occur exception", e); 46 | // } 47 | // } 48 | // 49 | // @Advice.OnMethodExit(onThrowable = Throwable.class) 50 | // public static void onMethodExit() { 51 | // MDCUtils.clear(); 52 | // } 53 | // 54 | //} 55 | -------------------------------------------------------------------------------- /common/src/main/java/com/itkevin/common/util/ByteBuddyPlugin.java: -------------------------------------------------------------------------------- 1 | //package com.itkevin.common.util; 2 | // 3 | //import com.xxl.job.core.handler.IJobHandler; 4 | //import net.bytebuddy.asm.Advice; 5 | //import net.bytebuddy.build.Plugin; 6 | //import net.bytebuddy.description.annotation.AnnotationList; 7 | //import net.bytebuddy.description.type.TypeDescription; 8 | //import net.bytebuddy.dynamic.ClassFileLocator; 9 | //import net.bytebuddy.dynamic.DynamicType; 10 | //import net.bytebuddy.matcher.ElementMatchers; 11 | // 12 | //import java.io.IOException; 13 | // 14 | //public class ByteBuddyPlugin implements Plugin { 15 | // 16 | // @Override 17 | // public DynamicType.Builder apply(DynamicType.Builder builder, TypeDescription typeDescription, ClassFileLocator classFileLocator) { 18 | // if (typeDescription.isAssignableTo(ConsumerCallback.class)) { 19 | // return builder.visit(Advice.to(ConsumerCallbackAdvisor.class).on(ElementMatchers.isOverriddenFrom(ConsumerCallback.class).and(ElementMatchers.named("call")))); 20 | // } else if (typeDescription.isAssignableTo(BatchConsumerCallback.class)) { 21 | // return builder.visit(Advice.to(BatchConsumerCallbackAdvisor.class).on(ElementMatchers.isOverriddenFrom(BatchConsumerCallback.class).and(ElementMatchers.named("call")))); 22 | // } else if (typeDescription.isAssignableTo(IJobHandler.class)) { 23 | // return builder.visit(Advice.to(JobHandlerAdvisor.class).on(ElementMatchers.isOverriddenFrom(IJobHandler.class).and(ElementMatchers.named("execute")))); 24 | // } else { 25 | // return builder.visit(Advice.to(EventProcessorAdvisor.class).on(ElementMatchers.isAnnotatedWith(EventProcessor.class))); 26 | // } 27 | // } 28 | // 29 | // @Override 30 | // public void close() throws IOException { 31 | // 32 | // } 33 | // 34 | // @Override 35 | // public boolean matches(TypeDescription typeDefinitions) { 36 | // boolean isConsumerCallback = typeDefinitions.isAssignableTo(ConsumerCallback.class); 37 | // boolean isBatchConsumerCallback = typeDefinitions.isAssignableTo(BatchConsumerCallback.class); 38 | // boolean isJobHander = typeDefinitions.isAssignableTo(IJobHandler.class); 39 | // AnnotationList annotationList = typeDefinitions.getDeclaredAnnotations(); 40 | // return isConsumerCallback || isBatchConsumerCallback || isJobHander || (!CollectionUtil.isEmpty(annotationList) && annotationList.isAnnotationPresent(EventProcessor.class)); 41 | // } 42 | //} 43 | -------------------------------------------------------------------------------- /common/src/main/java/com/itkevin/common/util/ByteBuddyUtils.java: -------------------------------------------------------------------------------- 1 | //package com.itkevin.common.util; 2 | // 3 | //import com.xxl.job.core.handler.IJobHandler; 4 | //import com.xxl.job.core.handler.annotation.JobHandler; 5 | //import lombok.extern.slf4j.Slf4j; 6 | //import net.bytebuddy.ByteBuddy; 7 | //import net.bytebuddy.agent.ByteBuddyAgent; 8 | //import net.bytebuddy.asm.Advice; 9 | //import net.bytebuddy.dynamic.loading.ClassReloadingStrategy; 10 | //import net.bytebuddy.matcher.ElementMatchers; 11 | //import org.springframework.aop.support.AopUtils; 12 | //import org.springframework.context.ApplicationContext; 13 | // 14 | //import java.util.Map; 15 | // 16 | //@Slf4j 17 | //public class ByteBuddyUtils { 18 | // 19 | // /** 20 | // * 字节码增强 21 | // * @param applicationContext 22 | // */ 23 | // public static void byteBuddy(ApplicationContext applicationContext) { 24 | // // RocketMq消息者回调实现类增强 25 | // byteBuddyMq(applicationContext); 26 | // // job任务类增强 27 | // byteBuddyJob(applicationContext); 28 | // } 29 | // 30 | // /** 31 | // * RocketMq消息者回调实现类增强 32 | // * @param applicationContext 33 | // */ 34 | // private static void byteBuddyMq(ApplicationContext applicationContext) { 35 | // Map consumerCallbackMap = applicationContext.getBeansOfType(ConsumerCallback.class); 36 | // if (!CollectionUtil.isEmpty(consumerCallbackMap)) { 37 | // consumerCallbackMap.forEach((name, consumerCallback) -> { 38 | // try{ 39 | // log.info("byteBuddyMq consumerCallback : {}, class {}",name,AopUtils.getTargetClass(consumerCallback)); 40 | // ByteBuddyAgent.install(); 41 | // new ByteBuddy().redefine(AopUtils.getTargetClass(consumerCallback)) 42 | // .visit(Advice.to(ConsumerCallbackAdvisor.class).on(ElementMatchers.isOverriddenFrom(ConsumerCallback.class).and(ElementMatchers.named("call")))) 43 | // .make() 44 | // .load(consumerCallback.getClass().getClassLoader(), ClassReloadingStrategy.fromInstalledAgent()); 45 | // }catch (Exception e){ 46 | // log.warn("byteBuddyMq error, e: {}",e.getMessage()); 47 | // } 48 | // 49 | // }); 50 | // } 51 | // Map batchConsumerCallbackMap = applicationContext.getBeansOfType(BatchConsumerCallback.class); 52 | // if (!CollectionUtil.isEmpty(batchConsumerCallbackMap)) { 53 | // batchConsumerCallbackMap.forEach((name, batchConsumerCallback) -> { 54 | // try{ 55 | // log.info("byteBuddyMq batchConsumerCallback : {}, class {}: ",name,AopUtils.getTargetClass(batchConsumerCallback)); 56 | // ByteBuddyAgent.install(); 57 | // new ByteBuddy().redefine(AopUtils.getTargetClass(batchConsumerCallback)) 58 | // .visit(Advice.to(BatchConsumerCallbackAdvisor.class).on(ElementMatchers.isOverriddenFrom(BatchConsumerCallback.class).and(ElementMatchers.named("call")))) 59 | // .make() 60 | // .load(batchConsumerCallback.getClass().getClassLoader(), ClassReloadingStrategy.fromInstalledAgent()); 61 | // }catch (Exception e){ 62 | // log.warn("byteBuddyMq error, e: {}",e.getMessage()); 63 | // } 64 | // }); 65 | // } 66 | // } 67 | // 68 | // /** 69 | // * job任务类增强 70 | // * @param applicationContext 71 | // */ 72 | // private static void byteBuddyJob(ApplicationContext applicationContext) { 73 | // Map jobHandlerMap = applicationContext.getBeansWithAnnotation(JobHandler.class); 74 | // if (!CollectionUtil.isEmpty(jobHandlerMap)) { 75 | // try{ 76 | // jobHandlerMap.forEach((name, jobHandler) -> { 77 | // log.info("byteBuddyJob : {},class : {}",name,AopUtils.getTargetClass(jobHandler)); 78 | // ByteBuddyAgent.install(); 79 | // new ByteBuddy().redefine(AopUtils.getTargetClass(jobHandler)) 80 | // .visit(Advice.to(JobHandlerAdvisor.class).on(ElementMatchers.isOverriddenFrom(IJobHandler.class).and(ElementMatchers.named("execute")))) 81 | // .make() 82 | // .load(jobHandler.getClass().getClassLoader(), ClassReloadingStrategy.fromInstalledAgent()); 83 | // }); 84 | // }catch (Exception e){ 85 | // log.warn("byteBuddyJob error, e: {}",e.getMessage()); 86 | // } 87 | // 88 | // } 89 | // } 90 | //} 91 | -------------------------------------------------------------------------------- /common/src/main/java/com/itkevin/common/util/CommonConverter.java: -------------------------------------------------------------------------------- 1 | package com.itkevin.common.util; 2 | 3 | import ma.glasnost.orika.MapperFacade; 4 | import ma.glasnost.orika.MapperFactory; 5 | import ma.glasnost.orika.impl.DefaultMapperFactory; 6 | 7 | public class CommonConverter { 8 | private static MapperFactory mapperFactory = new DefaultMapperFactory.Builder().build(); 9 | private static MapperFacade mapperFacade = null; 10 | 11 | static { 12 | CommonConverter.mapperFacade = CommonConverter.mapperFactory.getMapperFacade(); 13 | } 14 | 15 | private CommonConverter() { 16 | 17 | } 18 | 19 | public static MapperFacade getConverter() { 20 | return CommonConverter.mapperFacade; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /common/src/main/java/com/itkevin/common/util/ConfigUtils.java: -------------------------------------------------------------------------------- 1 | //package com.itkevin.common.util; 2 | // 3 | //import cn.hutool.core.util.NumberUtil; 4 | //import com.ctrip.framework.apollo.Config; 5 | //import com.ctrip.framework.apollo.ConfigService; 6 | //import lombok.extern.slf4j.Slf4j; 7 | //import org.apache.commons.lang3.StringUtils; 8 | // 9 | //import java.util.Properties; 10 | // 11 | //@Slf4j 12 | //public class ConfigUtils { 13 | // 14 | // /** 15 | // * 配置属性 16 | // */ 17 | // private static final Properties properties = new Properties(); 18 | // 19 | // /** 20 | // * config instance 21 | // * @return 22 | // */ 23 | // public static Config getConfig() { 24 | // return ConfigService.getConfig("skyeye"); 25 | // } 26 | // 27 | // /** 28 | // * 获取String类型配置 29 | // * @param key 30 | // * @param defaultValue 31 | // * @return 32 | // */ 33 | // public static String getProperty(String key, String defaultValue) { 34 | // if (StringUtils.isBlank(key)) { 35 | // return defaultValue; 36 | // } 37 | // try { 38 | // return properties.getProperty(key, defaultValue); 39 | // } catch (Exception e) { 40 | // return defaultValue; 41 | // } 42 | // } 43 | // 44 | // /** 45 | // * 获取Integer类型配置 46 | // * @param key 47 | // * @param defaultValue 48 | // * @return 49 | // */ 50 | // public static Integer getIntProperty(String key, Integer defaultValue) { 51 | // if (StringUtils.isBlank(key)) { 52 | // return defaultValue; 53 | // } 54 | // try { 55 | // Object o = properties.getOrDefault(key, defaultValue); 56 | // return NumberUtil.isInteger(o.toString()) ? Integer.parseInt(o.toString()) : defaultValue; 57 | // } catch (Exception e) { 58 | // return defaultValue; 59 | // } 60 | // } 61 | // 62 | // /** 63 | // * 获取Long类型配置 64 | // * @param key 65 | // * @param defaultValue 66 | // * @return 67 | // */ 68 | // public static Long getLongProperty(String key, Long defaultValue) { 69 | // if (StringUtils.isBlank(key)) { 70 | // return defaultValue; 71 | // } 72 | // try { 73 | // Object o = properties.getOrDefault(key, defaultValue); 74 | // return NumberUtil.isLong(o.toString()) ? Long.parseLong(o.toString()) : defaultValue; 75 | // } catch (Exception e) { 76 | // return defaultValue; 77 | // } 78 | // } 79 | // 80 | // /** 81 | // * 存储配置 82 | // * @param key 83 | // * @param value 84 | // */ 85 | // public static void saveProperty(String key, String value) { 86 | // if (StringUtils.isBlank(key) || value == null) { 87 | // return; 88 | // } 89 | // try { 90 | // properties.put(key, value); 91 | // } catch (Exception e) { 92 | // e.printStackTrace(); 93 | // } 94 | // } 95 | // 96 | // /** 97 | // * 删除配置 98 | // * @param key 99 | // */ 100 | // public static void removeProperty(String key) { 101 | // if (StringUtils.isBlank(key)) { 102 | // return; 103 | // } 104 | // try { 105 | // properties.remove(key); 106 | // } catch (Exception e) { 107 | // e.printStackTrace(); 108 | // } 109 | // } 110 | // 111 | //} 112 | -------------------------------------------------------------------------------- /common/src/main/java/com/itkevin/common/util/ConsumerCallbackAdvisor.java: -------------------------------------------------------------------------------- 1 | //package com.itkevin.common.util; 2 | // 3 | //import com.ctrip.framework.foundation.Foundation; 4 | //import com.skyeye.k12.teacher.common.enums.MDCConstantEnum; 5 | //import com.skyeye.k12.teacher.common.enums.RequestTypeEnum; 6 | //import lombok.extern.slf4j.Slf4j; 7 | //import net.bytebuddy.asm.Advice; 8 | //import org.apache.rocketmq.common.message.MessageExt; 9 | //import org.apache.skywalking.apm.toolkit.trace.TraceContext; 10 | // 11 | //import java.lang.reflect.Method; 12 | // 13 | //@Slf4j 14 | //public class ConsumerCallbackAdvisor { 15 | // 16 | // @Advice.OnMethodEnter 17 | // public static void onMethodEnter(@Advice.This Object object, @Advice.Origin Method method, @Advice.AllArguments Object[] arguments) { 18 | // try { 19 | // MessageExt messageExt = arguments != null && arguments.length > 0 ? (MessageExt) arguments[1] : null; 20 | // MDCUtils.put(MDCConstantEnum.SERVER_NAME.getCode(), Foundation.app().getAppId()); 21 | // MDCUtils.put(MDCConstantEnum.SERVER_IP.getCode(), IPUtils.getLocalIp()); 22 | // MDCUtils.put(MDCConstantEnum.SERVER_HOSTNAME.getCode(), IPUtils.getLocalHostName()); 23 | // MDCUtils.put(MDCConstantEnum.REQUEST_TYPE.getCode(), RequestTypeEnum.MQ.name().toLowerCase()); 24 | // MDCUtils.put(MDCConstantEnum.TRACE_ID.getCode(), TraceContext.traceId()); 25 | // MDCUtils.put(MDCConstantEnum.REQUEST_URI.getCode(), object.getClass().getName() + "#call"); 26 | // MDCUtils.put(MDCConstantEnum.MESSAGE_TOPIC.getCode(), messageExt != null ? messageExt.getTopic() : ""); 27 | // MDCUtils.put(MDCConstantEnum.MESSAGE_ID.getCode(), messageExt != null ? messageExt.getMsgId() : ""); 28 | // MDCUtils.put(MDCConstantEnum.MESSAGE_KEYS.getCode(), messageExt != null ? messageExt.getKeys() : ""); 29 | // } catch (Exception e) { 30 | // log.warn("log skyeye >>> ConsumerCallbackAdvisor.onMethodEnter occur exception", e); 31 | // } 32 | // } 33 | // 34 | // @Advice.OnMethodExit(onThrowable = Throwable.class) 35 | // public static void onMethodExit() { 36 | // MDCUtils.clear(); 37 | // } 38 | // 39 | //} 40 | -------------------------------------------------------------------------------- /common/src/main/java/com/itkevin/common/util/ElapsedUtils.java: -------------------------------------------------------------------------------- 1 | package com.itkevin.common.util; 2 | 3 | import cn.hutool.core.collection.CollectionUtil; 4 | import cn.hutool.json.JSONUtil; 5 | import com.google.common.collect.Lists; 6 | import com.itkevin.common.config.SysConfig; 7 | import com.itkevin.common.constants.SysConstant; 8 | import com.itkevin.common.enums.BusinessTypeEnum; 9 | import com.itkevin.common.enums.MDCConstantEnum; 10 | import com.itkevin.common.model.BusinessData; 11 | import com.itkevin.common.model.HashedWheelData; 12 | import com.itkevin.common.model.UriElapsedCollect; 13 | import com.itkevin.common.model.UriElapsedData; 14 | import lombok.extern.slf4j.Slf4j; 15 | import org.apache.commons.lang3.StringUtils; 16 | import reactor.core.publisher.Mono; 17 | import reactor.core.scheduler.Schedulers; 18 | 19 | import java.util.List; 20 | import java.util.concurrent.ExecutorService; 21 | import java.util.concurrent.LinkedBlockingQueue; 22 | import java.util.concurrent.ThreadPoolExecutor; 23 | import java.util.concurrent.TimeUnit; 24 | import java.util.stream.Collectors; 25 | 26 | @Slf4j 27 | public class ElapsedUtils { 28 | 29 | /** 30 | * 接口耗时统一前缀 31 | */ 32 | public static final String URI_ELAPSED = "URI_ELAPSED_"; 33 | 34 | /** 35 | * 接口耗时traceId集合统一后缀 36 | */ 37 | public static final String URI_ELAPSED_TRACEID_LIST = "_URI_ELAPSED_TRACEID_LIST"; 38 | 39 | /** 40 | * 接口最大耗时统一后缀 41 | */ 42 | public static final String MAX_URI_ELAPSED = "_MAX_URI_ELAPSED"; 43 | 44 | /** 45 | * 接口最大耗时traceId统一后缀 46 | */ 47 | public static final String MAX_URI_ELAPSED_TRACEID = "_MAX_URI_ELAPSED_TRACEID"; 48 | 49 | /** 50 | * 接口uri耗时聚合-指定线程池 51 | */ 52 | private static ExecutorService executorService = new ThreadPoolExecutor(SysConstant.THREAD_NUM, SysConstant.MAX_THREAD_NUM, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<>(1), new ThreadPoolExecutor.DiscardPolicy()); 53 | 54 | /** 55 | * 接口uri耗时聚合 56 | * @param uriElapsedCollect 57 | */ 58 | public static void uriElapsed(UriElapsedCollect uriElapsedCollect) { 59 | Mono.fromRunnable(() -> { 60 | try { 61 | long elapsed = uriElapsedCollect.getElapsed(); 62 | String requestURI = uriElapsedCollect.getRequestURI(); 63 | if (StringUtils.isNotBlank(requestURI)) { 64 | requestURI = requestURI.replaceAll("/\\d+", "/{PathVariable}"); 65 | // 接口耗时报警间隔时间、接口耗时超过阀值时间次数 66 | Integer alarmUriElapsedTime = SysConfig.instance.getAlarmUriElapsedTime(); 67 | Integer alarmUriElapsedCount = SysConfig.instance.getAlarmUriElapsedCount(); 68 | if (alarmUriElapsedTime == null || alarmUriElapsedCount == null || alarmUriElapsedTime == 0 || alarmUriElapsedCount == 0) { 69 | return; 70 | } 71 | // 获取指定URI接口耗时时间阀值 72 | long elapsedThreshold = getUriElapsedThreshold(requestURI); 73 | // 耗时超过阀值 74 | if (elapsed > elapsedThreshold) { 75 | // 耗时超过阀值的次数 76 | String uniqueKey = URI_ELAPSED + requestURI; 77 | // 存储缓存数据 78 | storageData(uniqueKey, uriElapsedCollect); 79 | // 计算次数 80 | Integer count = LocalCacheUtils.incr(uniqueKey); 81 | if (count.compareTo(1) == 0) { 82 | // 第一次耗时超过阀值时则给时间轮上添加任务 83 | BusinessData businessData = new BusinessData(); 84 | businessData.setRequestURI(requestURI); 85 | businessData.setServerName(uriElapsedCollect.getServerName()); 86 | businessData.setServerIP(uriElapsedCollect.getServerIP()); 87 | businessData.setServerHostname(uriElapsedCollect.getServerHostname()); 88 | HashedWheelUtils.putWheelQueue(new HashedWheelData(alarmUriElapsedTime, BusinessTypeEnum.URI_ELAPSED.name(), uniqueKey, JSONUtil.toJsonStr(businessData))); 89 | } 90 | } 91 | } 92 | } catch (Throwable e) { 93 | log.warn("log skyeye >>> ElapsedUtils.uriElapsed occur exception", e); 94 | } 95 | }).subscribeOn(Schedulers.fromExecutorService(executorService, "skyeye-uriElapsed-executor")).subscribe(); 96 | } 97 | 98 | /** 99 | * 获取指定URI接口耗时时间阀值 100 | * @param requestURI 101 | * @return 102 | */ 103 | public static long getUriElapsedThreshold(String requestURI) { 104 | if (StringUtils.isBlank(requestURI)) { 105 | return 0; 106 | } 107 | // 获取指定URI耗时时间阀值 108 | String alarmUriElapsed = SysConfig.instance.getAlarmUriElapsed(); 109 | List uriElapsedDataList = StringUtils.isNotBlank(alarmUriElapsed) ? JSONUtil.toList(JSONUtil.parseArray(alarmUriElapsed), UriElapsedData.class) : Lists.newArrayList(); 110 | uriElapsedDataList = uriElapsedDataList.stream() 111 | .filter(uriElapsedData -> StringUtils.isNotBlank(uriElapsedData.getUri())) 112 | .peek(uriElapsedData -> uriElapsedData.setUri(uriElapsedData.getUri().replaceAll("/\\d+", "/{PathVariable}"))) 113 | .collect(Collectors.toList()); 114 | List elapsedList = uriElapsedDataList.stream().filter(uriElapsedData -> uriElapsedData.getUri().equalsIgnoreCase(requestURI)).map(UriElapsedData::getElapsed).collect(Collectors.toList()); 115 | // 获取全局接口耗时时间阀值 116 | Long alarmUriElapsedGlobal = SysConfig.instance.getAlarmUriElapsedGlobal(); 117 | 118 | return CollectionUtil.isEmpty(elapsedList) ? alarmUriElapsedGlobal : elapsedList.get(0); 119 | } 120 | 121 | /** 122 | * 存储缓存数据 123 | * @param uriElapsedCollect 124 | */ 125 | private static void storageData(String uniqueKey, UriElapsedCollect uriElapsedCollect) { 126 | storageTraceidListData(uniqueKey, uriElapsedCollect.getTraceId()); 127 | storageMaxElapsedData(uniqueKey, uriElapsedCollect.getElapsed(), uriElapsedCollect.getTraceId()); 128 | } 129 | 130 | /** 131 | * 存储traceIdList 132 | * @param uniqueKey 133 | * @param traceId 134 | */ 135 | private static synchronized void storageTraceidListData(String uniqueKey, String traceId) { 136 | String traceIdListKey = uniqueKey + URI_ELAPSED_TRACEID_LIST; 137 | List traceIds = LocalCacheUtils.smember(traceIdListKey); 138 | if (CollectionUtil.isEmpty(traceIds)) { 139 | LocalCacheUtils.sadd(traceIdListKey, traceId); 140 | } else { 141 | if (traceIds.size() < 3) { 142 | LocalCacheUtils.sadd(traceIdListKey, traceId); 143 | } 144 | } 145 | } 146 | 147 | /** 148 | * 存储最大耗时和最大耗时traceId 149 | * @param uniqueKey 150 | * @param elapsed 151 | * @param traceId 152 | */ 153 | private static synchronized void storageMaxElapsedData(String uniqueKey, long elapsed, String traceId) { 154 | String maxElapsedKey = uniqueKey + MAX_URI_ELAPSED; 155 | String maxElapsedTraceIdKey = uniqueKey + MAX_URI_ELAPSED + MAX_URI_ELAPSED_TRACEID; 156 | Long maxElapsed = LocalCacheUtils.getLongCache(maxElapsedKey); 157 | if (elapsed > maxElapsed) { 158 | LocalCacheUtils.putLongCache(maxElapsedKey, elapsed); 159 | LocalCacheUtils.putCache(maxElapsedTraceIdKey, traceId); 160 | } 161 | } 162 | 163 | /** 164 | * URI数据采集 165 | * @param elapsed 166 | * @return 167 | */ 168 | public static UriElapsedCollect uriElapsedCollect(long elapsed) { 169 | UriElapsedCollect uriElapsedCollect = new UriElapsedCollect(); 170 | uriElapsedCollect.setRequestURI(MDCUtils.get(MDCConstantEnum.REQUEST_URI.getCode())); 171 | uriElapsedCollect.setElapsed(elapsed); 172 | uriElapsedCollect.setTraceId(MDCUtils.get(MDCConstantEnum.TRACE_ID.getCode())); 173 | uriElapsedCollect.setServerName(MDCUtils.get(MDCConstantEnum.SERVER_NAME.getCode())); 174 | uriElapsedCollect.setServerIP(MDCUtils.get(MDCConstantEnum.SERVER_IP.getCode())); 175 | uriElapsedCollect.setServerHostname(MDCUtils.get(MDCConstantEnum.SERVER_HOSTNAME.getCode())); 176 | 177 | return uriElapsedCollect; 178 | } 179 | } 180 | -------------------------------------------------------------------------------- /common/src/main/java/com/itkevin/common/util/EventProcessorAdvisor.java: -------------------------------------------------------------------------------- 1 | //package com.itkevin.common.util; 2 | // 3 | //import com.ctrip.framework.foundation.Foundation; 4 | //import com.itkevin.common.enums.MDCConstantEnum; 5 | //import com.itkevin.common.enums.RequestTypeEnum; 6 | //import lombok.extern.slf4j.Slf4j; 7 | //import net.bytebuddy.asm.Advice; 8 | //import org.apache.skywalking.apm.toolkit.trace.TraceContext; 9 | //import sun.plugin2.message.EventMessage; 10 | // 11 | //import java.lang.reflect.Method; 12 | // 13 | //@Slf4j 14 | //public class EventProcessorAdvisor { 15 | // 16 | // @Advice.OnMethodEnter 17 | // public static void onMethodEnter(@Advice.This Object object, @Advice.Origin Method method, @Advice.AllArguments Object[] arguments) { 18 | // try { 19 | // Object argument = arguments != null && arguments.length > 0 ? arguments[0] : null; 20 | // EventMessage eventMessage = argument instanceof EventMessage ? (EventMessage) argument : null; 21 | // MDCUtils.put(MDCConstantEnum.SERVER_NAME.getCode(), Foundation.app().getAppId()); 22 | // MDCUtils.put(MDCConstantEnum.SERVER_IP.getCode(), IPUtils.getLocalIp()); 23 | // MDCUtils.put(MDCConstantEnum.SERVER_HOSTNAME.getCode(), IPUtils.getLocalHostName()); 24 | // MDCUtils.put(MDCConstantEnum.REQUEST_TYPE.getCode(), RequestTypeEnum.EVENT.name().toLowerCase()); 25 | // MDCUtils.put(MDCConstantEnum.TRACE_ID.getCode(), TraceContext.traceId()); 26 | // MDCUtils.put(MDCConstantEnum.REQUEST_URI.getCode(), object.getClass().getName() + "#" + method.getName()); 27 | // MDCUtils.put(MDCConstantEnum.EVENT_NAME.getCode(), eventMessage != null ? eventMessage.getEventName() : ""); 28 | // MDCUtils.put(MDCConstantEnum.EVENT_PAYLOAD.getCode(), eventMessage != null ? eventMessage.getPayload() : ""); 29 | // } catch (Exception e) { 30 | // log.warn("log skyeye >>> EventProcessorAdvisor.onMethodEnter occur exception", e); 31 | // } 32 | // } 33 | // 34 | // @Advice.OnMethodExit(onThrowable = Throwable.class) 35 | // public static void onMethodExit() { 36 | // MDCUtils.clear(); 37 | // } 38 | // 39 | //} 40 | -------------------------------------------------------------------------------- /common/src/main/java/com/itkevin/common/util/GuavaCacheUtils.java: -------------------------------------------------------------------------------- 1 | package com.itkevin.common.util; 2 | 3 | import cn.hutool.core.collection.CollectionUtil; 4 | import com.google.common.cache.Cache; 5 | import com.google.common.cache.CacheBuilder; 6 | import lombok.extern.slf4j.Slf4j; 7 | import org.apache.commons.lang3.StringUtils; 8 | 9 | import java.util.Calendar; 10 | import java.util.GregorianCalendar; 11 | import java.util.List; 12 | import java.util.concurrent.Callable; 13 | import java.util.concurrent.TimeUnit; 14 | 15 | @Slf4j 16 | public class GuavaCacheUtils { 17 | 18 | /** 19 | * 缓存操作对象 20 | */ 21 | private static Cache cache; 22 | 23 | static { 24 | cache = CacheBuilder.newBuilder() 25 | .maximumSize(1000) 26 | .expireAfterWrite(1, TimeUnit.HOURS) 27 | //.removalListener(notification -> log.info("log skyeye >>> {} was removed from guava cache, cause is {}", notification.getKey(), notification.getCause())) 28 | .build(); 29 | } 30 | 31 | /** 32 | * 当天剩余时间 33 | * @return 34 | */ 35 | private static long getMilliSeconds() { 36 | Calendar curDate = Calendar.getInstance(); 37 | Calendar tomorrowDate = new GregorianCalendar(curDate.get(Calendar.YEAR), 38 | curDate.get(Calendar.MONTH), 39 | curDate.get(Calendar.DATE) + 1, 0, 0, 0); 40 | return tomorrowDate.getTimeInMillis() - System.currentTimeMillis(); 41 | } 42 | 43 | /** 44 | * 获取缓存值 45 | * 46 | * @param key 47 | * @return 48 | */ 49 | protected static Object get(String key) { 50 | String value = null; 51 | try { 52 | if (StringUtils.isNotBlank(key)) { 53 | return cache.getIfPresent(key); 54 | } 55 | } catch (Exception e) { 56 | e.printStackTrace(); 57 | } 58 | 59 | return value; 60 | } 61 | 62 | /** 63 | * 获取缓存值,如果值不存在返回默认值 64 | * 65 | * @param key 66 | * @return 67 | */ 68 | protected static Object get(String key, String defaultValue) { 69 | Object value = null; 70 | try { 71 | if (StringUtils.isNotBlank(key)) { 72 | value = cache.getIfPresent(key); 73 | value = value != null ? value : defaultValue; 74 | } 75 | } catch (Exception e) { 76 | e.printStackTrace(); 77 | } 78 | 79 | return value; 80 | } 81 | 82 | /** 83 | * 获取缓存值,如果值不存在执行回调方法 84 | * 85 | * @param key 86 | * @param loader 87 | * @return 88 | */ 89 | protected static Object get(String key, Callable loader) { 90 | Object value = null; 91 | try { 92 | if (StringUtils.isNotBlank(key) && loader != null) { 93 | return cache.get(key, loader); 94 | } 95 | } catch (Exception e) { 96 | e.printStackTrace(); 97 | } 98 | 99 | return value; 100 | } 101 | 102 | /** 103 | * 放入缓存 104 | * 105 | * @param key 106 | * @param value 107 | */ 108 | protected static void put(String key, Object value) { 109 | try { 110 | if (StringUtils.isNotEmpty(key) && value != null) { 111 | cache.put(key, value); 112 | } 113 | } catch (Exception e) { 114 | e.printStackTrace(); 115 | } 116 | } 117 | 118 | /** 119 | * 移除缓存 120 | * 121 | * @param key 122 | */ 123 | protected static void remove(String key) { 124 | try { 125 | if (StringUtils.isNotEmpty(key)) { 126 | cache.invalidate(key); 127 | } 128 | } catch (Exception e) { 129 | e.printStackTrace(); 130 | } 131 | } 132 | 133 | /** 134 | * 批量移除缓存 135 | * 136 | * @param keys 137 | */ 138 | protected static void remove(List keys) { 139 | try { 140 | if (!CollectionUtil.isEmpty(keys)) { 141 | cache.invalidateAll(keys); 142 | } 143 | } catch (Exception e) { 144 | e.printStackTrace(); 145 | } 146 | } 147 | 148 | /** 149 | * 清空缓存 150 | */ 151 | protected static void removeAll() { 152 | try { 153 | cache.invalidateAll(); 154 | } catch (Exception e) { 155 | e.printStackTrace(); 156 | } 157 | } 158 | 159 | } 160 | -------------------------------------------------------------------------------- /common/src/main/java/com/itkevin/common/util/HashedWheelTask.java: -------------------------------------------------------------------------------- 1 | package com.itkevin.common.util; 2 | 3 | import java.util.concurrent.Executors; 4 | import java.util.concurrent.ScheduledExecutorService; 5 | import java.util.concurrent.TimeUnit; 6 | 7 | public class HashedWheelTask { 8 | 9 | /** 10 | * 调度线程池 11 | */ 12 | private static ScheduledExecutorService service = Executors.newScheduledThreadPool(1); 13 | 14 | /** 15 | * 初始化 16 | */ 17 | public static void init() { 18 | service.scheduleAtFixedRate(HashedWheelUtils::task, 1, 1, TimeUnit.MINUTES); 19 | } 20 | 21 | } 22 | -------------------------------------------------------------------------------- /common/src/main/java/com/itkevin/common/util/HashedWheelUtils.java: -------------------------------------------------------------------------------- 1 | package com.itkevin.common.util; 2 | 3 | import cn.hutool.core.collection.CollectionUtil; 4 | import cn.hutool.json.JSONUtil; 5 | import com.itkevin.common.config.SysConfig; 6 | import com.itkevin.common.constants.SysConstant; 7 | import com.itkevin.common.enums.BusinessTypeEnum; 8 | import com.itkevin.common.model.*; 9 | import com.itkevin.common.notice.NotifyMessageTools; 10 | import lombok.extern.slf4j.Slf4j; 11 | import org.apache.commons.lang3.StringUtils; 12 | import reactor.core.publisher.Mono; 13 | import reactor.core.scheduler.Schedulers; 14 | 15 | import java.util.Collections; 16 | import java.util.List; 17 | import java.util.concurrent.ExecutorService; 18 | import java.util.concurrent.LinkedBlockingQueue; 19 | import java.util.concurrent.ThreadPoolExecutor; 20 | import java.util.concurrent.TimeUnit; 21 | import java.util.concurrent.atomic.AtomicInteger; 22 | 23 | @Slf4j 24 | public class HashedWheelUtils { 25 | 26 | /** 27 | * 时间轮大小 28 | */ 29 | public static final int WHEEL_SIZE = 60; 30 | 31 | /** 32 | * 时间轮当前位置 33 | */ 34 | public static final String WHEEL_CURRENT_INDEX = "WHEEL_CURRENT_INDEX"; 35 | 36 | /** 37 | * 时间轮延迟位置 38 | */ 39 | public static final String WHEEL_WAIT_INDEX = "WHEEL_WAIT_INDEX"; 40 | 41 | /** 42 | * 发送消息时间轮数据-指定线程池 43 | */ 44 | private static ExecutorService executorService = new ThreadPoolExecutor(SysConstant.THREAD_NUM, SysConstant.MAX_THREAD_NUM, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<>(1), new ThreadPoolExecutor.DiscardPolicy()); 45 | 46 | /** 47 | * intGaugeNumber 48 | */ 49 | private static AtomicInteger gaugeNumber = SimpleMetricsUtils.getIntGaugeNumber(SysConstant.ALARM_METRIC_NAME, Collections.emptyList(), Collections.emptyList()); 50 | 51 | /** 52 | * 将任务加入到时间轮 53 | * @param hashedWheelData 54 | */ 55 | public static void putWheelQueue(HashedWheelData hashedWheelData) { 56 | // 任务模型 57 | TaskModel taskModel = new TaskModel(); 58 | taskModel.setBusinessId(hashedWheelData.getBusinessId()); 59 | int cycleNum = 0; 60 | if (hashedWheelData.getDelayTime() > WHEEL_SIZE) { 61 | cycleNum = hashedWheelData.getDelayTime() / WHEEL_SIZE; 62 | } 63 | taskModel.setCycleNum(cycleNum); 64 | // 任务数据放入缓存 65 | LocalCacheUtils.putCache(hashedWheelData.getBusinessId(), JSONUtil.toJsonStr(hashedWheelData)); 66 | // 获取时间轮当前位置 67 | Integer currentIndex = LocalCacheUtils.getIntCache(WHEEL_CURRENT_INDEX); 68 | // 计算任务存放位置 69 | int offset = hashedWheelData.getDelayTime() - WHEEL_SIZE * cycleNum; 70 | if (currentIndex + offset > WHEEL_SIZE) { 71 | currentIndex = currentIndex + offset - WHEEL_SIZE; 72 | } else { 73 | currentIndex += offset; 74 | } 75 | // 任务加入时间轮 76 | LocalCacheUtils.sadd(WHEEL_WAIT_INDEX + currentIndex, JSONUtil.toJsonStr(taskModel)); 77 | } 78 | 79 | /** 80 | * 时间轮任务 81 | */ 82 | protected synchronized static void task() { 83 | Integer currentIndex = LocalCacheUtils.incr(WHEEL_CURRENT_INDEX); 84 | if (currentIndex > WHEEL_SIZE) { 85 | currentIndex = currentIndex - WHEEL_SIZE; 86 | LocalCacheUtils.putIntCache(WHEEL_CURRENT_INDEX, currentIndex); 87 | } 88 | // 获取时间轮当前位置的任务列表 89 | List list = LocalCacheUtils.smember(WHEEL_WAIT_INDEX + currentIndex); 90 | if (!CollectionUtil.isEmpty(list)) { 91 | // 循环处理任务 92 | for (String task : list) { 93 | String businessId = ""; 94 | try { 95 | TaskModel taskModel = JSONUtil.toBean(task, TaskModel.class); 96 | businessId = taskModel.getBusinessId(); 97 | if (taskModel.getCycleNum() == 0) { 98 | String data = LocalCacheUtils.getCache(businessId); 99 | if (StringUtils.isNotBlank(data)) { 100 | HashedWheelData hashedWheelData = JSONUtil.toBean(data, HashedWheelData.class); 101 | handleHashedWheelData(hashedWheelData); 102 | LocalCacheUtils.srem(WHEEL_WAIT_INDEX + currentIndex, task); 103 | LocalCacheUtils.delCache(businessId); 104 | LocalCacheUtils.delIntCache(hashedWheelData.getBusinessId()); 105 | LocalCacheUtils.delLongCache(businessId + ElapsedUtils.MAX_URI_ELAPSED); 106 | LocalCacheUtils.delCache(businessId + ElapsedUtils.MAX_URI_ELAPSED + ElapsedUtils.MAX_URI_ELAPSED_TRACEID); 107 | LocalCacheUtils.delCache(businessId + ElapsedUtils.URI_ELAPSED_TRACEID_LIST); 108 | } 109 | } else { 110 | taskModel.setCycleNum(taskModel.getCycleNum() - 1); 111 | LocalCacheUtils.srem(WHEEL_WAIT_INDEX + currentIndex, task); 112 | LocalCacheUtils.sadd(WHEEL_WAIT_INDEX + currentIndex, JSONUtil.toJsonStr(taskModel)); 113 | } 114 | } catch (Exception e) { 115 | log.warn("log skyeye >>> HashedWheelTask occur exception, businessId: {}", businessId, e); 116 | LocalCacheUtils.srem(WHEEL_WAIT_INDEX + currentIndex, task); 117 | LocalCacheUtils.delCache(businessId); 118 | LocalCacheUtils.delIntCache(businessId); 119 | LocalCacheUtils.delLongCache(businessId + ElapsedUtils.MAX_URI_ELAPSED); 120 | LocalCacheUtils.delCache(businessId + ElapsedUtils.MAX_URI_ELAPSED + ElapsedUtils.MAX_URI_ELAPSED_TRACEID); 121 | LocalCacheUtils.delCache(businessId + ElapsedUtils.URI_ELAPSED_TRACEID_LIST); 122 | } 123 | } 124 | } 125 | // 异常报警上报 126 | Integer skyAlarmNum = LocalCacheUtils.getIntCache(SysConstant.ALARM_METRIC_NAME); 127 | SimpleMetricsUtils.setIntGaugeNumber(gaugeNumber, skyAlarmNum); 128 | LocalCacheUtils.putIntCache(SysConstant.ALARM_METRIC_NAME, 0); 129 | } 130 | 131 | /** 132 | * 处理时间轮任务数据 133 | * @param hashedWheelData 134 | */ 135 | private static void handleHashedWheelData(HashedWheelData hashedWheelData) { 136 | String businessType = hashedWheelData.getBusinessType(); 137 | if (BusinessTypeEnum.NOTIFY.name().equalsIgnoreCase(businessType)) { 138 | Integer alarmNotifyTime = SysConfig.instance.getAlarmNotifyTime(); 139 | Integer alarmNotifyCount = SysConfig.instance.getAlarmNotifyCount(); 140 | Integer count = LocalCacheUtils.getIntCache(hashedWheelData.getBusinessId()); 141 | // 实际报警次数超过阀值则发送聚合报警消息 142 | if (count > alarmNotifyCount) { 143 | BusinessData businessData = JSONUtil.toBean(hashedWheelData.getBusinessData(), BusinessData.class); 144 | LogCompressData logCompressData = CommonConverter.getConverter().map(businessData, LogCompressData.class); 145 | logCompressData.setAlarmTime(alarmNotifyTime); 146 | logCompressData.setAlarmCount(count); 147 | Mono.fromRunnable(() -> { 148 | try { 149 | NotifyMessageTools.getInstance().sendAlarmTalk(logCompressData); 150 | } catch (Throwable e) { 151 | log.warn("log skyeye >>> HashedWheelTask.handleHashedWheelData[{}] occur exception", businessType, e); 152 | } 153 | }).subscribeOn(Schedulers.fromExecutorService(executorService, "skyeye-sendMessage-hashedWheelData-executor")).subscribe(); 154 | } 155 | } 156 | if (BusinessTypeEnum.URI_ELAPSED.name().equalsIgnoreCase(businessType)) { 157 | Integer alarmUriElapsedTime = SysConfig.instance.getAlarmUriElapsedTime(); 158 | Integer alarmUriElapsedCount = SysConfig.instance.getAlarmUriElapsedCount(); 159 | Integer count = LocalCacheUtils.getIntCache(hashedWheelData.getBusinessId()); 160 | // 耗时大于指定时间的次数超过阀值则发送聚合消息 161 | if (count > alarmUriElapsedCount) { 162 | BusinessData businessData = JSONUtil.toBean(hashedWheelData.getBusinessData(), BusinessData.class); 163 | LogUriElapsedData logUriElapsedData = CommonConverter.getConverter().map(businessData, LogUriElapsedData.class); 164 | logUriElapsedData.setAlarmTime(alarmUriElapsedTime); 165 | logUriElapsedData.setAlarmCount(count); 166 | logUriElapsedData.setUriElapsedThreshold(ElapsedUtils.getUriElapsedThreshold(businessData.getRequestURI())); 167 | logUriElapsedData.setTraceIdList(LocalCacheUtils.smember(hashedWheelData.getBusinessId() + ElapsedUtils.URI_ELAPSED_TRACEID_LIST)); 168 | logUriElapsedData.setMaxUriElapsed(LocalCacheUtils.getLongCache(hashedWheelData.getBusinessId() + ElapsedUtils.MAX_URI_ELAPSED)); 169 | logUriElapsedData.setMaxUriElapsedTraceId(LocalCacheUtils.getCache(hashedWheelData.getBusinessId() + ElapsedUtils.MAX_URI_ELAPSED + ElapsedUtils.MAX_URI_ELAPSED_TRACEID)); 170 | Mono.fromRunnable(() -> { 171 | try { 172 | NotifyMessageTools.getInstance().sendAlarmTalk(logUriElapsedData); 173 | } catch (Throwable e) { 174 | log.warn("log skyeye >>> HashedWheelTask.handleHashedWheelData[{}] occur exception", businessType, e); 175 | } 176 | }).subscribeOn(Schedulers.fromExecutorService(executorService, "skyeye-sendMessage-hashedWheelData-executor")).subscribe(); 177 | } 178 | } 179 | } 180 | } 181 | -------------------------------------------------------------------------------- /common/src/main/java/com/itkevin/common/util/IPUtils.java: -------------------------------------------------------------------------------- 1 | package com.itkevin.common.util; 2 | 3 | import org.apache.commons.lang3.StringUtils; 4 | 5 | import java.io.IOException; 6 | import java.io.InputStream; 7 | import java.net.InetAddress; 8 | import java.net.NetworkInterface; 9 | import java.net.UnknownHostException; 10 | import java.util.Enumeration; 11 | 12 | public class IPUtils { 13 | private static String hostName; 14 | 15 | /** 16 | * 获取本机ip 17 | * @return 18 | */ 19 | public static String getLocalIp() { 20 | try { 21 | //一个主机有多个网络接口 22 | Enumeration netInterfaces = NetworkInterface.getNetworkInterfaces(); 23 | while (netInterfaces.hasMoreElements()) { 24 | NetworkInterface netInterface = netInterfaces.nextElement(); 25 | //每个网络接口,都会有多个"网络地址",比如一定会有loopback地址,会有siteLocal地址等.以及IPV4或者IPV6 . 26 | Enumeration addresses = netInterface.getInetAddresses(); 27 | while (addresses.hasMoreElements()) { 28 | InetAddress address = addresses.nextElement(); 29 | //get only :172.*,192.*,10.* 30 | if (address.isSiteLocalAddress() && !address.isLoopbackAddress()) { 31 | return address.getHostAddress(); 32 | } 33 | } 34 | } 35 | }catch (Exception e) { 36 | e.printStackTrace(); 37 | return null; 38 | } 39 | return null; 40 | } 41 | 42 | /** 43 | * 获取本机hostname 44 | * @return 45 | */ 46 | public static String getLocalHostName() { 47 | if (StringUtils.isNotBlank(hostName)) { 48 | return hostName; 49 | } 50 | String hostname = null; 51 | try { 52 | hostname = InetAddress.getLocalHost().getHostName(); 53 | } catch (UnknownHostException uhe) { 54 | String host = uhe.getMessage(); 55 | if (host != null) { 56 | int colon = host.indexOf(':'); 57 | if (colon > 0) { 58 | return host.substring(0, colon); 59 | } 60 | } 61 | } 62 | if (StringUtils.isNotBlank(hostname) && hostname.contains("-")) { 63 | hostName = hostname; 64 | return hostname; 65 | } 66 | InputStream is = null; 67 | try { 68 | Process p = Runtime.getRuntime().exec("hostname"); 69 | byte[] hostBytes = new byte[1024]; 70 | is = p.getInputStream(); 71 | int readed = is.read(hostBytes); 72 | p.waitFor(); 73 | hostName = new String(hostBytes, 0, readed); 74 | if (StringUtils.isNotBlank(hostName)) { 75 | hostName = hostName.trim(); 76 | } 77 | return hostName; 78 | } catch (Throwable e) { 79 | e.printStackTrace(); 80 | } finally { 81 | if (is != null) { 82 | try { 83 | is.close(); 84 | } catch (IOException e) { 85 | e.printStackTrace(); 86 | } 87 | } 88 | } 89 | hostName = "unknownHostname"; 90 | return hostName; 91 | } 92 | 93 | } 94 | -------------------------------------------------------------------------------- /common/src/main/java/com/itkevin/common/util/JobHandlerAdvisor.java: -------------------------------------------------------------------------------- 1 | package com.itkevin.common.util; 2 | 3 | import com.ctrip.framework.foundation.Foundation; 4 | import com.itkevin.common.enums.MDCConstantEnum; 5 | import com.itkevin.common.enums.RequestTypeEnum; 6 | import lombok.extern.slf4j.Slf4j; 7 | import net.bytebuddy.asm.Advice; 8 | import org.apache.skywalking.apm.toolkit.trace.TraceContext; 9 | 10 | import java.lang.reflect.Method; 11 | 12 | @Slf4j 13 | public class JobHandlerAdvisor { 14 | 15 | @Advice.OnMethodEnter 16 | public static void onMethodEnter(@Advice.This Object object, @Advice.Origin Method method, @Advice.AllArguments Object[] arguments) { 17 | try { 18 | String requestParam = arguments != null && arguments.length > 0 ? (String) arguments[0] : ""; 19 | MDCUtils.put(MDCConstantEnum.SERVER_NAME.getCode(), Foundation.app().getAppId()); 20 | MDCUtils.put(MDCConstantEnum.SERVER_IP.getCode(), IPUtils.getLocalIp()); 21 | MDCUtils.put(MDCConstantEnum.SERVER_HOSTNAME.getCode(), IPUtils.getLocalHostName()); 22 | MDCUtils.put(MDCConstantEnum.REQUEST_TYPE.getCode(), RequestTypeEnum.JOB.name().toLowerCase()); 23 | MDCUtils.put(MDCConstantEnum.TRACE_ID.getCode(), TraceContext.traceId()); 24 | MDCUtils.put(MDCConstantEnum.REQUEST_URI.getCode(), object.getClass().getName() + "#execute"); 25 | MDCUtils.put(MDCConstantEnum.REQUEST_PARAM.getCode(), requestParam); 26 | } catch (Exception e) { 27 | log.warn("log skyeye >>> JobHandlerAdvisor.onMethodEnter occur exception", e); 28 | } 29 | } 30 | 31 | @Advice.OnMethodExit(onThrowable = Throwable.class) 32 | public static void onMethodExit() { 33 | MDCUtils.clear(); 34 | } 35 | 36 | } 37 | -------------------------------------------------------------------------------- /common/src/main/java/com/itkevin/common/util/LocalCacheUtils.java: -------------------------------------------------------------------------------- 1 | package com.itkevin.common.util; 2 | 3 | import cn.hutool.json.JSONUtil; 4 | import com.itkevin.common.model.TaskModel; 5 | import org.apache.commons.lang3.StringUtils; 6 | 7 | import java.util.List; 8 | import java.util.concurrent.CopyOnWriteArrayList; 9 | import java.util.concurrent.atomic.AtomicInteger; 10 | import java.util.concurrent.atomic.AtomicLong; 11 | import java.util.concurrent.atomic.AtomicReference; 12 | 13 | public class LocalCacheUtils { 14 | 15 | /** 16 | * int前缀 17 | */ 18 | private static final String KEY_INT = "LOG_SKYEYE_INT_"; 19 | 20 | /** 21 | * alarm前缀 22 | */ 23 | private static final String KEY_ALARM = "LOG_SKYEYE_ALARM_"; 24 | 25 | /** 26 | * 对key的value值做加1操作 27 | * @param key 28 | * @return 29 | */ 30 | public static Integer incr(String key) { 31 | if (StringUtils.isBlank(key)) { 32 | return 0; 33 | } 34 | try { 35 | key = KEY_INT + key; 36 | AtomicInteger atomicInteger = (AtomicInteger) GuavaCacheUtils.get(key, AtomicInteger::new); 37 | return atomicInteger.incrementAndGet(); 38 | } catch (Exception e) { 39 | e.printStackTrace(); 40 | } 41 | return 0; 42 | } 43 | 44 | /** 45 | * 存储int缓存 46 | */ 47 | public static void putIntCache(String key, Integer value) { 48 | try { 49 | if (StringUtils.isNotBlank(key) && value != null) { 50 | key = KEY_INT + key; 51 | GuavaCacheUtils.put(key, new AtomicInteger(value)); 52 | } 53 | } catch (Exception e) { 54 | e.printStackTrace(); 55 | } 56 | } 57 | 58 | /** 59 | * 获取int缓存 60 | */ 61 | public static Integer getIntCache(String key) { 62 | try { 63 | if (StringUtils.isNotBlank(key)) { 64 | key = KEY_INT + key; 65 | AtomicInteger atomicInteger = (AtomicInteger) GuavaCacheUtils.get(key, AtomicInteger::new); 66 | return atomicInteger.get(); 67 | } 68 | } catch (Exception e) { 69 | e.printStackTrace(); 70 | } 71 | return 0; 72 | } 73 | 74 | /** 75 | * 删除int缓存 76 | * @param key 77 | */ 78 | protected static void delIntCache(String key) { 79 | try { 80 | if (StringUtils.isNotBlank(key)) { 81 | key = KEY_INT + key; 82 | GuavaCacheUtils.remove(key); 83 | } 84 | } catch (Exception e) { 85 | e.printStackTrace(); 86 | } 87 | } 88 | 89 | /** 90 | * 存储long缓存 91 | */ 92 | protected static void putLongCache(String key, Long value) { 93 | try { 94 | if (StringUtils.isNotBlank(key) && value != null) { 95 | key = KEY_INT + key; 96 | GuavaCacheUtils.put(key, new AtomicLong(value)); 97 | } 98 | } catch (Exception e) { 99 | e.printStackTrace(); 100 | } 101 | } 102 | 103 | /** 104 | * 获取long缓存 105 | */ 106 | protected static Long getLongCache(String key) { 107 | try { 108 | if (StringUtils.isNotBlank(key)) { 109 | key = KEY_INT + key; 110 | AtomicLong atomicLong = (AtomicLong) GuavaCacheUtils.get(key, AtomicLong::new); 111 | return atomicLong.get(); 112 | } 113 | } catch (Exception e) { 114 | e.printStackTrace(); 115 | } 116 | return 0L; 117 | } 118 | 119 | /** 120 | * 删除long缓存 121 | * @param key 122 | */ 123 | protected static void delLongCache(String key) { 124 | try { 125 | if (StringUtils.isNotBlank(key)) { 126 | key = KEY_INT + key; 127 | GuavaCacheUtils.remove(key); 128 | } 129 | } catch (Exception e) { 130 | e.printStackTrace(); 131 | } 132 | } 133 | 134 | /** 135 | * 存储缓存 136 | * @param key 137 | * @param value 138 | */ 139 | protected static void putCache(String key, String value) { 140 | try { 141 | if (StringUtils.isNotBlank(key) && value != null) { 142 | key = KEY_ALARM + key; 143 | GuavaCacheUtils.put(key, new AtomicReference<>(value)); 144 | } 145 | } catch (Exception e) { 146 | e.printStackTrace(); 147 | } 148 | } 149 | 150 | /** 151 | * 获取缓存 152 | * @param key 153 | * @return 154 | */ 155 | protected static String getCache(String key) { 156 | try { 157 | if (StringUtils.isNotBlank(key)) { 158 | key = KEY_ALARM + key; 159 | AtomicReference atomicReference = (AtomicReference) GuavaCacheUtils.get(key, AtomicReference::new); 160 | Object object = atomicReference.get(); 161 | if (object instanceof String) { 162 | return atomicReference.get().toString(); 163 | } 164 | } 165 | } catch (Exception e) { 166 | e.printStackTrace(); 167 | } 168 | return null; 169 | } 170 | 171 | /** 172 | * 删除缓存 173 | * @param key 174 | */ 175 | protected static void delCache(String key) { 176 | try { 177 | if (StringUtils.isNotBlank(key)) { 178 | key = KEY_ALARM + key; 179 | GuavaCacheUtils.remove(key); 180 | } 181 | } catch (Exception e) { 182 | e.printStackTrace(); 183 | } 184 | } 185 | 186 | /** 187 | * 存储集合缓存 188 | * @param key 189 | * @param value 190 | */ 191 | protected static void sadd(String key, String value) { 192 | try { 193 | if (StringUtils.isNotBlank(key) && value != null) { 194 | key = KEY_ALARM + key; 195 | CopyOnWriteArrayList list = (CopyOnWriteArrayList) GuavaCacheUtils.get(key, CopyOnWriteArrayList::new); 196 | list.add(value); 197 | } 198 | } catch (Exception e) { 199 | e.printStackTrace(); 200 | } 201 | } 202 | 203 | /** 204 | * 获取集合缓存 205 | * @param key 206 | * @return 207 | */ 208 | protected static List smember(String key) { 209 | try { 210 | if (StringUtils.isNotBlank(key)) { 211 | key = KEY_ALARM + key; 212 | return (CopyOnWriteArrayList) GuavaCacheUtils.get(key, CopyOnWriteArrayList::new); 213 | } 214 | } catch (Exception e) { 215 | e.printStackTrace(); 216 | } 217 | return null; 218 | } 219 | 220 | /** 221 | * 删除集合缓存元素 222 | * @param key 223 | * @param value 224 | */ 225 | protected static void srem(String key, String value) { 226 | try { 227 | if (StringUtils.isNotBlank(key) && value != null) { 228 | key = KEY_ALARM + key; 229 | CopyOnWriteArrayList list = (CopyOnWriteArrayList) GuavaCacheUtils.get(key, CopyOnWriteArrayList::new); 230 | list.removeIf(s -> s.equals(value)); 231 | } 232 | } catch (Exception e) { 233 | e.printStackTrace(); 234 | } 235 | } 236 | 237 | public static void main(String[] args) { 238 | TaskModel taskModel = new TaskModel(); 239 | taskModel.setBusinessId("hashcode"); 240 | taskModel.setCycleNum(1); 241 | sadd("key", JSONUtil.toJsonStr(taskModel)); 242 | sadd("key", JSONUtil.toJsonStr(taskModel)); 243 | System.out.println(smember("key")); 244 | srem("key", JSONUtil.toJsonStr(taskModel)); 245 | System.out.println(smember("key")); 246 | } 247 | } 248 | -------------------------------------------------------------------------------- /common/src/main/java/com/itkevin/common/util/LogUtils.java: -------------------------------------------------------------------------------- 1 | package com.itkevin.common.util; 2 | 3 | import com.ctrip.framework.foundation.Foundation; 4 | import com.itkevin.common.config.SysConfig; 5 | import com.itkevin.common.constants.SysConstant; 6 | import com.itkevin.common.enums.LogLevelEnum; 7 | import com.itkevin.common.enums.MDCConstantEnum; 8 | import com.itkevin.common.model.FilterMessage; 9 | import com.itkevin.common.model.LogData; 10 | import org.apache.commons.lang3.StringUtils; 11 | 12 | import java.util.regex.Matcher; 13 | import java.util.regex.Pattern; 14 | 15 | public class LogUtils { 16 | 17 | /** 18 | * 异常过滤 19 | * 20 | * @param filterMessage 21 | * @return 22 | */ 23 | public static boolean filter(FilterMessage filterMessage) { 24 | String logType = filterMessage.getLogType(); 25 | String msg = filterMessage.getMsg(); 26 | String message = filterMessage.getMessage(); 27 | String stackTrace = filterMessage.getStackTrace(); 28 | // 特殊error信息过滤 29 | if (StringUtils.isNotBlank(msg)) { 30 | // rocketmq打印的error过滤掉 31 | if (msg.contains("consume topic:") && msg.contains("consumer:") && msg.contains("msg:") && msg.contains("msgId:") && msg.contains("bornTimestamp:") 32 | || msg.contains("topic:") && msg.contains("consumer:") && msg.contains("msgSize:")) 33 | return true; 34 | } 35 | // 获取配置 36 | String alarmWhiteList = SysConfig.instance.getAlarmWhiteList(); 37 | 38 | return filter(msg, alarmWhiteList) || filter(message, alarmWhiteList) || filter(stackTrace, alarmWhiteList); 39 | } 40 | 41 | /** 42 | * 获取LogData数据对象 43 | * 44 | * @param logType 45 | * @param msg 46 | * @param message 47 | * @param stackTrace 48 | * @return 49 | */ 50 | public static LogData obtainLogData(String logType, String msg, String message, String stackTrace) { 51 | // 获取配置 52 | int stackNum = SysConfig.instance.getAlarmStacknum(); 53 | // logData 54 | LogData logData = new LogData(); 55 | logData.setLevel(getLogLevel(msg)); 56 | msg = StringUtils.isNotBlank(msg) ? msg.replaceAll("header(.*)", "").trim() : ""; 57 | msg = StringUtils.isNotBlank(msg) ? msg.replaceAll("headers(.*)", "").trim() : ""; 58 | logData.setErrorMessage(msg); 59 | logData.setServerName(Foundation.app().getAppId()); 60 | logData.setServerIP(IPUtils.getLocalIp()); 61 | logData.setServerHostname(IPUtils.getLocalHostName()); 62 | logData.setSourceIP(MDCUtils.get(MDCConstantEnum.SOURCE_IP.getCode())); 63 | logData.setRequestType(MDCUtils.get(MDCConstantEnum.REQUEST_TYPE.getCode())); 64 | logData.setTraceId(MDCUtils.get(MDCConstantEnum.TRACE_ID.getCode())); 65 | logData.setRequestURI(MDCUtils.get(MDCConstantEnum.REQUEST_URI.getCode())); 66 | logData.setRequestParam(MDCUtils.get(MDCConstantEnum.REQUEST_PARAM.getCode())); 67 | logData.setMessageTopic(MDCUtils.get(MDCConstantEnum.MESSAGE_TOPIC.getCode())); 68 | logData.setMessageId(MDCUtils.get(MDCConstantEnum.MESSAGE_ID.getCode())); 69 | logData.setMessageKeys(MDCUtils.get(MDCConstantEnum.MESSAGE_KEYS.getCode())); 70 | logData.setEventName(MDCUtils.get(MDCConstantEnum.EVENT_NAME.getCode())); 71 | logData.setEventPayload(MDCUtils.get(MDCConstantEnum.EVENT_PAYLOAD.getCode())); 72 | logData.setExceptionMessage(message); 73 | String exceptionStackTrace = getRegexContent(stackTrace, stackNum); 74 | if (!exceptionStackTrace.contains("Caused by")) { 75 | String causedByContent = getCausedByContentOfStackTrace(stackTrace, 1); 76 | exceptionStackTrace += causedByContent; 77 | } 78 | String regex = "(dubbo)://[-A-Za-z0-9+&@#/%?=~_|!:,.;]+[-A-Za-z0-9+&@#/%=~_|]"; 79 | exceptionStackTrace = exceptionStackTrace.replaceAll(regex,"XXX"); 80 | logData.setExceptionStackTrace(exceptionStackTrace); 81 | 82 | return logData; 83 | } 84 | 85 | /** 86 | * 信息过滤 87 | * 88 | * @param msg 89 | * @param filter 90 | * @return 91 | */ 92 | public static boolean filter(String msg, String filter) { 93 | if (StringUtils.isNotBlank(msg) && StringUtils.isNotBlank(filter)) { 94 | String[] filters = filter.split(","); 95 | for (String filt : filters) { 96 | if (msg.contains(filt)) { 97 | return true; 98 | } 99 | } 100 | } 101 | 102 | return false; 103 | } 104 | 105 | /** 106 | * 日志级别 107 | * 108 | * @param msg 109 | * @return 110 | */ 111 | private static String getLogLevel(String msg) { 112 | String level = LogLevelEnum.NORMAL.name(); 113 | if (StringUtils.isNotBlank(msg)) { 114 | return msg.toLowerCase().contains("level") && msg.toLowerCase().contains("serious") ? LogLevelEnum.SERIOUS.name() : LogLevelEnum.NORMAL.name(); 115 | } 116 | 117 | return level; 118 | } 119 | 120 | /** 121 | * 字符串正则截取 122 | * 123 | * @param content 124 | * @param seq 125 | * @return 126 | */ 127 | private static String getRegexContent(String content, int seq) { 128 | Matcher slashMatcher = Pattern.compile(System.getProperty("line.separator")).matcher(content); 129 | int mIdx = 0; 130 | do { 131 | if (!slashMatcher.find()) { 132 | return content; 133 | } 134 | ++mIdx; 135 | } while(mIdx != seq); 136 | 137 | return content.substring(0, slashMatcher.start()); 138 | } 139 | 140 | /** 141 | * 截取异常堆栈的Caused by内容 142 | * 143 | * @param stackTrace 144 | * @param stackNum 145 | * @return 146 | */ 147 | private static String getCausedByContentOfStackTrace(String stackTrace, int stackNum) { 148 | String content = ""; 149 | if (stackTrace.contains("Caused by")) { 150 | content = System.getProperty("line.separator") + getRegexContent(stackTrace.substring(stackTrace.indexOf("Caused by")), stackNum); 151 | } 152 | 153 | return content; 154 | } 155 | 156 | } 157 | -------------------------------------------------------------------------------- /common/src/main/java/com/itkevin/common/util/MD5Utils.java: -------------------------------------------------------------------------------- 1 | package com.itkevin.common.util; 2 | 3 | import java.security.MessageDigest; 4 | import java.security.NoSuchAlgorithmException; 5 | 6 | public class MD5Utils { 7 | 8 | /** 9 | * md5加密 10 | * @param plainText 11 | * @return 12 | */ 13 | public static String encryption(String plainText) { 14 | String re_md5 = new String(); 15 | try { 16 | MessageDigest md = MessageDigest.getInstance("MD5"); 17 | md.update(plainText.getBytes()); 18 | byte b[] = md.digest(); 19 | int i; 20 | StringBuffer buf = new StringBuffer(""); 21 | for (int offset = 0; offset < b.length; offset++) { 22 | i = b[offset]; 23 | if (i < 0) 24 | i += 256; 25 | if (i < 16) 26 | buf.append("0"); 27 | buf.append(Integer.toHexString(i)); 28 | } 29 | re_md5 = buf.toString(); 30 | } catch (NoSuchAlgorithmException e) { 31 | e.printStackTrace(); 32 | } 33 | return re_md5; 34 | } 35 | 36 | public static void main(String[] args) { 37 | String str = ""; 38 | String resultStr = encryption(str); 39 | System.out.println("MD5加密结果:"+resultStr); 40 | } 41 | 42 | } 43 | -------------------------------------------------------------------------------- /common/src/main/java/com/itkevin/common/util/MDCUtils.java: -------------------------------------------------------------------------------- 1 | package com.itkevin.common.util; 2 | 3 | import cn.hutool.core.collection.CollectionUtil; 4 | import org.apache.commons.lang3.StringUtils; 5 | import org.slf4j.MDC; 6 | 7 | import java.util.Map; 8 | 9 | public class MDCUtils { 10 | 11 | /** 12 | * MDC 获取数据 13 | * @param key 14 | * @return 15 | */ 16 | public static String get(String key) { 17 | String value = null; 18 | try { 19 | if (StringUtils.isNotBlank(key)) { 20 | value = MDC.get(key); 21 | } 22 | } catch (Exception e) { 23 | e.printStackTrace(); 24 | } 25 | 26 | return value; 27 | } 28 | 29 | /** 30 | * MDC 获取数据 31 | * @param key 32 | * @param defaultValue 33 | * @return 34 | */ 35 | public static String get(String key,String defaultValue) { 36 | String value = null; 37 | try { 38 | if (StringUtils.isNotBlank(key)) { 39 | value = MDC.get(key); 40 | value = value != null ? value : defaultValue; 41 | } 42 | } catch (Exception e) { 43 | e.printStackTrace(); 44 | } 45 | 46 | return value; 47 | } 48 | 49 | /** 50 | * MDC put数据 51 | * @param key 52 | * @param value 53 | */ 54 | public static void put(String key,String value) { 55 | try { 56 | if (StringUtils.isNotEmpty(key) && value != null) { 57 | MDC.put(key,value); 58 | } 59 | } catch (Exception e) { 60 | e.printStackTrace(); 61 | } 62 | } 63 | 64 | /** 65 | * 复制一份MDC 66 | * @return 67 | */ 68 | public static Map getCopyMDC() { 69 | try { 70 | return MDC.getCopyOfContextMap(); 71 | } catch (Exception e) { 72 | e.printStackTrace(); 73 | return null; 74 | } 75 | } 76 | 77 | /** 78 | * 设置MDC 79 | * @param mdc 80 | */ 81 | public static void setMDC(Map mdc) { 82 | try { 83 | if (!CollectionUtil.isEmpty(mdc)) { 84 | MDC.setContextMap(mdc); 85 | } 86 | } catch (Exception e) { 87 | e.printStackTrace(); 88 | } 89 | } 90 | 91 | /** 92 | * MDC删除 93 | * @param key 94 | */ 95 | public static void remove(String key) { 96 | try { 97 | if (StringUtils.isNotBlank(key)) { 98 | MDC.remove(key); 99 | } 100 | } catch (Exception e) { 101 | e.printStackTrace(); 102 | } 103 | } 104 | 105 | /** 106 | * MDC清理 107 | */ 108 | public static void clear() { 109 | try { 110 | MDC.clear(); 111 | } catch (Exception e) { 112 | e.printStackTrace(); 113 | } 114 | } 115 | 116 | } 117 | -------------------------------------------------------------------------------- /common/src/main/java/com/itkevin/common/util/PropertiesUtils.java: -------------------------------------------------------------------------------- 1 | package com.itkevin.common.util; 2 | 3 | import lombok.extern.slf4j.Slf4j; 4 | import org.apache.commons.lang3.StringUtils; 5 | import org.slf4j.Logger; 6 | import org.slf4j.LoggerFactory; 7 | 8 | import java.io.FileInputStream; 9 | import java.io.InputStream; 10 | import java.net.URL; 11 | import java.util.ArrayList; 12 | import java.util.Enumeration; 13 | import java.util.List; 14 | import java.util.Properties; 15 | 16 | @Slf4j 17 | public class PropertiesUtils { 18 | 19 | private static final Logger logger = LoggerFactory.getLogger(PropertiesUtils.class); 20 | 21 | private PropertiesUtils() { 22 | } 23 | 24 | private static volatile Properties PROPERTIES; 25 | 26 | public static final String SYSTEM_GLOBAL = "SystemGlobals.properties"; 27 | 28 | public static Properties getProperties() { 29 | if (PROPERTIES == null) { 30 | synchronized (PropertiesUtils.class) { 31 | if (PROPERTIES == null) { 32 | String path = System.getProperty(SYSTEM_GLOBAL); 33 | if (path == null || path.length() == 0) { 34 | path = System.getenv(SYSTEM_GLOBAL); 35 | if (path == null || path.length() == 0) { 36 | path = SYSTEM_GLOBAL; 37 | } 38 | } 39 | PROPERTIES = loadProperties(path, false, true); 40 | } 41 | } 42 | } 43 | return PROPERTIES; 44 | } 45 | 46 | public static void addProperties(Properties properties) { 47 | if (properties != null) { 48 | getProperties().putAll(properties); 49 | } 50 | } 51 | 52 | public static void setProperties(Properties properties) { 53 | if (properties != null) { 54 | PROPERTIES = properties; 55 | } 56 | } 57 | 58 | public static String getProperty(String key) { 59 | return getProperty(key, null); 60 | } 61 | 62 | public static String getProperty(String key, String defaultValue) { 63 | String value = System.getProperty(key); 64 | if (value != null && value.trim().length() == 0) { 65 | return null; 66 | } 67 | if (value != null) { 68 | return value; 69 | } 70 | Properties properties = getProperties(); 71 | value = properties.getProperty(key); 72 | if (StringUtils.isNotBlank(value)) { 73 | return value; 74 | } else { 75 | return defaultValue; 76 | } 77 | } 78 | 79 | public static Properties loadProperties(String fileName) { 80 | return loadProperties(fileName, false, false); 81 | } 82 | 83 | public static Properties loadProperties(String fileName, boolean allowMultiFile) { 84 | return loadProperties(fileName, allowMultiFile, false); 85 | } 86 | 87 | public static Properties loadProperties(String fileName, boolean allowMultiFile, boolean optional) { 88 | Properties properties = new Properties(); 89 | if (fileName.startsWith("/")) { 90 | try { 91 | FileInputStream input = new FileInputStream(fileName); 92 | try { 93 | properties.load(input); 94 | } 95 | finally { 96 | input.close(); 97 | } 98 | } 99 | catch (Throwable e) { 100 | logger.warn( 101 | "Failed to load " + fileName + " file from " + fileName + "(ingore this file): " 102 | + e.getMessage(), e); 103 | } 104 | return properties; 105 | } 106 | 107 | List list = new ArrayList(); 108 | try { 109 | Enumeration urls = PropertiesUtils.class.getClassLoader().getResources(fileName); 110 | list = new ArrayList(); 111 | while (urls.hasMoreElements()) { 112 | list.add(urls.nextElement()); 113 | } 114 | } 115 | catch (Throwable t) { 116 | logger.warn("Fail to load " + fileName + " file: " + t.getMessage(), t); 117 | } 118 | 119 | if (list.size() == 0) { 120 | if (!optional) { 121 | logger.warn("No " + fileName + " found on the class path."); 122 | } 123 | return properties; 124 | } 125 | 126 | if (!allowMultiFile) { 127 | if (list.size() > 1) { 128 | String errMsg = String.format( 129 | "only 1 %s file is expected, but %d dubbo.properties files found on class path: %s", fileName, 130 | list.size(), list.toString()); 131 | logger.warn(errMsg); 132 | // throw new IllegalStateException(errMsg); // see 133 | // http://code.alibabatech.com/jira/browse/DUBBO-133 134 | } 135 | 136 | // fall back to use method getResourceAsStream 137 | try { 138 | properties.load(PropertiesUtils.class.getClassLoader().getResourceAsStream(fileName)); 139 | } 140 | catch (Throwable e) { 141 | logger.warn( 142 | "Failed to load " + fileName + " file from " + fileName + "(ingore this file): " 143 | + e.getMessage(), e); 144 | } 145 | return properties; 146 | } 147 | 148 | logger.info("load " + fileName + " properties file from " + list); 149 | 150 | for (URL url : list) { 151 | try { 152 | Properties p = new Properties(); 153 | InputStream input = url.openStream(); 154 | if (input != null) { 155 | try { 156 | p.load(input); 157 | properties.putAll(p); 158 | } 159 | finally { 160 | try { 161 | input.close(); 162 | } 163 | catch (Throwable t) {} 164 | } 165 | } 166 | } 167 | catch (Throwable e) { 168 | logger.warn("Fail to load " + fileName + " file from " + url + "(ingore this file): " + e.getMessage(), 169 | e); 170 | } 171 | } 172 | 173 | return properties; 174 | } 175 | 176 | public static void setProperty(String key, String value) { 177 | 178 | Properties properties = getProperties(); 179 | properties.setProperty(key, value); 180 | } 181 | } 182 | -------------------------------------------------------------------------------- /common/src/main/java/com/itkevin/common/util/SimpleMetricsUtils.java: -------------------------------------------------------------------------------- 1 | package com.itkevin.common.util; 2 | 3 | import com.ctrip.framework.apollo.core.enums.Env; 4 | import com.ctrip.framework.foundation.Foundation; 5 | import com.google.common.collect.ImmutableList; 6 | import io.micrometer.core.instrument.Metrics; 7 | import io.micrometer.core.instrument.Tags; 8 | import lombok.extern.slf4j.Slf4j; 9 | import org.apache.commons.lang3.ArrayUtils; 10 | 11 | import java.util.Collection; 12 | import java.util.List; 13 | import java.util.concurrent.atomic.AtomicInteger; 14 | import java.util.stream.IntStream; 15 | 16 | @Slf4j 17 | public class SimpleMetricsUtils { 18 | 19 | /** 20 | * 累计计数 21 | * @param name 22 | * @param tags 23 | * @param values 24 | * @param amount 25 | */ 26 | public static void counter(String name, List tags, List values, Double amount) { 27 | try { 28 | if (filter()) return; 29 | String[] tagValues = getTagValues(tags, values); 30 | // Monitors.count(name, amount, tagValues); 31 | } catch (Exception e) { 32 | log.warn("log skyeye >>> SimpleMetricsUtils.counter occur exception", e); 33 | } 34 | } 35 | 36 | /** 37 | * 获取intGaugeNumber 38 | * @param name 39 | * @param tags 40 | * @param values 41 | * @return 42 | */ 43 | public static AtomicInteger getIntGaugeNumber(String name, List tags, List values) { 44 | AtomicInteger number = new AtomicInteger(0); 45 | try { 46 | if (filter()) return number; 47 | String[] tagValues = getTagValues(tags, values); 48 | number = Metrics.gauge(name, Tags.of(tagValues), new AtomicInteger(0)); 49 | } catch (Exception e) { 50 | log.warn("log skyeye >>> SimpleMetricsUtils.getIntGaugeNumber occur exception", e); 51 | } 52 | return number; 53 | } 54 | 55 | /** 56 | * 设置intGaugeNumber 57 | * @param number 58 | * @param amount 59 | */ 60 | public static void setIntGaugeNumber(AtomicInteger number, Integer amount) { 61 | try { 62 | if (filter()) return; 63 | number.set(amount); 64 | } catch (Exception e) { 65 | log.warn("log skyeye >>> SimpleMetricsUtils.setIntGaugeNumber occur exception", e); 66 | } 67 | } 68 | 69 | /** 70 | * 过滤上报 71 | * @return 72 | */ 73 | private static boolean filter() { 74 | boolean filter = false; 75 | String envName = Foundation.server().getEnvType(); 76 | // if (Env.YUFA.name().equalsIgnoreCase(envName)) { 77 | // filter = true; 78 | // } 79 | return filter; 80 | } 81 | 82 | /** 83 | * 获取标签数组 84 | * @param tags 85 | * @param values 86 | * @return 87 | */ 88 | private static String[] getTagValues(List tags, List values) { 89 | return ArrayUtils.toStringArray( 90 | IntStream.range(0, Math.min(tags.size(), values.size())).mapToObj(index -> ImmutableList.of(tags.get(index), values.get(index))) 91 | .flatMap(Collection::stream).toArray() 92 | ); 93 | } 94 | 95 | } 96 | -------------------------------------------------------------------------------- /common/src/main/java/com/itkevin/common/util/StringConverterFactory.java: -------------------------------------------------------------------------------- 1 | package com.itkevin.common.util; 2 | 3 | import okhttp3.MediaType; 4 | import okhttp3.RequestBody; 5 | import okhttp3.ResponseBody; 6 | import retrofit2.Converter; 7 | import retrofit2.Retrofit; 8 | 9 | import java.lang.annotation.Annotation; 10 | import java.lang.reflect.Type; 11 | 12 | /** 13 | * retrofit2 String转换器 14 | */ 15 | public class StringConverterFactory extends Converter.Factory { 16 | 17 | @Override 18 | public Converter responseBodyConverter(Type type, Annotation[] annotations, Retrofit retrofit) { 19 | return (Converter) ResponseBody::string; 20 | } 21 | 22 | @Override 23 | public Converter requestBodyConverter(Type type, Annotation[] parameterAnnotations, Annotation[] methodAnnotations, Retrofit retrofit) { 24 | return (Converter) value -> RequestBody.create(MediaType.parse("text/plain"), value); 25 | } 26 | 27 | } 28 | -------------------------------------------------------------------------------- /common/src/main/resources/META-INF/services/com.itkevin.common.config.ConfigTool: -------------------------------------------------------------------------------- 1 | com.itkevin.common.config.ApolloConfigTool -------------------------------------------------------------------------------- /common/src/main/resources/META-INF/services/com.itkevin.common.notice.NoticeInterface: -------------------------------------------------------------------------------- 1 | com.itkevin.common.notice.dingding.DingTalkNotice 2 | com.itkevin.common.notice.workwx.WorkWeiXinTalkNotice -------------------------------------------------------------------------------- /common/src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /common/src/main/resources/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | logback 5 | 6 | 7 | 8 | 9 | %d{HH:mm:ss.SSS} %contextName [%thread] %-5level %logger{36} - %msg%n 10 | 11 | 12 | 13 | 14 | ${log.path} 15 | 16 | ${log.path}.%d{yyyy-MM-dd}.zip 17 | 18 | 19 | %date %level [%thread] %logger{36} [%file : %line] %msg%n 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /img/img.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kevin0016/sky-eye/c667f5f785859eccdc50f67c31cedd16a56b4358/img/img.png -------------------------------------------------------------------------------- /img/主要特性.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kevin0016/sky-eye/c667f5f785859eccdc50f67c31cedd16a56b4358/img/主要特性.png -------------------------------------------------------------------------------- /img/报警种类.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kevin0016/sky-eye/c667f5f785859eccdc50f67c31cedd16a56b4358/img/报警种类.png -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | com.itkevin 8 | sky-eye 9 | pom 10 | 2.0 11 | 12 | api 13 | common 14 | common-web 15 | common-dubbo 16 | 17 | 18 | 19 | 8 20 | 8 21 | 22 | 23 | 24 | 25 | com.squareup.okhttp3 26 | okhttp 27 | 3.14.4 28 | 29 | 30 | 31 | org.jsoup 32 | jsoup 33 | 1.12.1 34 | 35 | 36 | 37 | 38 | cn.hutool 39 | hutool-all 40 | 5.3.2 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /userManual.md: -------------------------------------------------------------------------------- 1 | ###### 1、引入包 2 | ```xml 3 | 4 | 5 | com.itkevin 6 | log4j-api 7 | 1.0.0 8 | 9 | 10 | 11 | 12 | com.itkevin 13 | logback-api 14 | 1.0.0 15 | 16 | ``` 17 | 18 | ###### 2、设置设置启动参数 19 | ```java 20 | // log4j 21 | com.itkevin.log4j.api.listener.Log4jApplicationListener 22 | // logback 23 | com.itkevin.logback.api.listener.LogbackApplicationListener 24 | // 以上两个类交由spring 管理 25 | ``` 26 | ###### 3、apollo配置参数 27 | 首先需要创建对应的namespace名称为:skyeye 28 | ```properties 29 | # 是否启动报警 30 | skyeye.log.alarm.enabled = true 31 | # 报警钉钉严重错误机器人配置(支持多个机器人) 32 | skyeye.log.alarm.serious.dingtalk = [ { "webHook": "https://oapi.dingtalk.com/robot/send?access_token=xxxxx", "secret": "xxxx" } ] 33 | # 报警钉钉机器人配置(支持多个机器人) 34 | skyeye.log.alarm.dingtalk = [ { "webHook": "https://oapi.dingtalk.com/robot/send?access_token=xxx", "secret": "xxxx" } ] 35 | # 堆栈行数配置 36 | skyeye.log.alarm.stackNum = 10 37 | # 单条报警白名单 38 | skyeye.log.alarm.white.list = 我是白名单 39 | # 聚合报警白名单 40 | skyeye.log.alarm.aggre.white.list = 我是聚合白名单 41 | # 报警间隔时间(单位分钟) 42 | skyeye.log.alarm.notify.time = 1 43 | # 报警次数阀值 44 | skyeye.log.alarm.notify.count = 1 45 | # 接口耗时报警间隔时间(单位分钟) 46 | skyeye.log.alarm.uri.elapsed.time = 1 47 | # 接口耗时超过阀值时间的次数阀值(阀值时间如果不指定则默认1000毫秒) 48 | skyeye.log.alarm.uri.elapsed.count = 10 49 | # 指定URI接口耗时时间阀值(单位毫秒,支持指定多个URI) 50 | skyeye.log.alarm.uri.elapsed = [{"uri":"/user/logTest","elapsed":2000}] 51 | # 指定接口耗时时间阀值(单位毫秒,全局指定,不配置默认1000毫秒) 52 | skyeye.log.alarm.uri.elapsed.global = 1000 53 | ``` 54 | ###### 备注 55 | - 目前接口超时报警仅支持web,后期会同步支持部分的rpc框架 56 | - 目前仅支持apollo配置和钉钉报警,后期会开放配置接口,可自行选择配置,报警接口也会支持企业微信和飞书,并且开放通知接口,可自定接入报警 57 | 58 | --------------------------------------------------------------------------------