├── .gitignore ├── LICENSE ├── README.md ├── config ├── address │ └── address.go ├── config.go ├── const.go └── logger.go ├── control ├── etc ├── address.yml └── probe.yml ├── gitversion ├── go.mod ├── go.sum ├── http ├── health.go ├── middleware │ ├── logger.go │ └── recovery.go ├── route.go └── start.go ├── install.sh ├── main.go ├── probe ├── core │ ├── clients.go │ ├── common.go │ ├── dataobj.go │ └── push.go ├── cron.go ├── func.go ├── models │ ├── ping.go │ ├── probe.go │ └── probe_test.go ├── ping.go └── url.go └── service └── probe.service /.gitignore: -------------------------------------------------------------------------------- 1 | *.exe 2 | n9e-probe 3 | etc/probe.local.yml 4 | etc/address.local.yml 5 | logs/ 6 | -------------------------------------------------------------------------------- /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 | # n9e-probe 2 | 3 | ## 功能 4 | ping 和 http get 请求探测 5 | 适配 [nightingale](https://github.com/didi/nightingale) 6 | 7 | ### 指标 8 | #### ping 9 | |metric|说明| 10 | |--|--| 11 | |ping.latency|ping 请求的延迟,单位是毫秒。-1 表示 ping 不通| 12 | 13 | |tag|说明| 14 | |--|--| 15 | |ip|探测的目标 ip| 16 | |region|如果配置了,则插入 region tag| 17 | 18 | #### url 19 | |metric|说明| 20 | |--|--| 21 | |url.latency|http 请求的延迟,单位是毫秒。-1 表示无法访问| 22 | |url.cert|证书探测。1正常,-1不正常。http 站点则是0| 23 | |url.status_code|返回的状态码| 24 | 25 | |tag|说明| 26 | |--|--| 27 | |host|目标 host| 28 | |scheme|目标 scheme| 29 | |path|目标的 path| 30 | |region|如果配置了,则插入 region tag| 31 | 32 | ### 配置 33 | #### address.yml 34 | ```yml 35 | --- 36 | transfer: 37 | http: 0.0.0.0:8008 38 | rpc: 0.0.0.0:8009 39 | addresses: 40 | - 192.168.0.100 # 修改成实际的 n9e 服务器地址 41 | 42 | probe: 43 | http: 127.0.0.1:2059 44 | ``` 45 | #### probe.yml 46 | ```yml 47 | logger: 48 | dir: logs/ 49 | level: INFO 50 | keepHours: 24 51 | 52 | probe: 53 | # 如果需要区分来自不同区域的探针,可以通过在配置 region 来插入 tag 54 | #region: default 55 | timeout: 5 # 探测的超时时间,单位是秒 56 | limit: 10 # 并发限制 57 | interval: 30 # 请求的间隔 58 | headers: # 插入到 http 请求中的 headers,可以多条 59 | user-agent: Mozilla/5.0 (Linux; Android 6.0.1; Moto G (4)) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Mobile Safari/537.36 Edg/87.0.664.66 60 | 61 | ping: 62 | 107: # n9e 节点上的 nid 号 63 | - 114.114.114.114 # 要探测的 ip 地址列表 64 | - 114.114.115.115 65 | 66 | url: 67 | 107: # n9e 节点上的 nid 号 68 | - https://www.baidu.com # 要探测的 url 地址列表 69 | - https://www.sjtu.edu.cn/ 70 | - https://bbs.ngacn.cc 71 | - https://www.163.com 72 | ``` 73 | 74 | ## 编译 75 | ``` 76 | # cd /home 77 | # git clone https://github.com/shanghai-edu/n9e-probe.git 78 | # cd n9e-probe 79 | # ./control build 80 | ``` 81 | 也可以直接在 release 中下载打包好的二进制 82 | ## 运行 83 | ### 支持 `systemctl` 的操作系统,如 `CentOS7` 84 | 执行 `install.sh` 脚本即可,`systemctl` 将托管运行 85 | 86 | ``` 87 | # ./install.sh 88 | Created symlink from /etc/systemd/system/multi-user.target.wants/agent.service to /usr/lib/systemd/system/agent.service. 89 | ``` 90 | 后续可通过 `systemctl start/stop/restart probe` 来进行服务管理 91 | 92 | 注意如果没有安装在 `/home` 路径上,则需要修改 `service/agent.service` 中的相关路径,否则 `systemctl` 注册时会找不到 93 | 94 | ### 不支持 systemctl 的操作系统 95 | 执行 `./control start` 启动即可 96 | ``` 97 | # ./control start 98 | probe started 99 | ``` 100 | 后续可通过 `./control start/stop/restart` 来进行服务管理 -------------------------------------------------------------------------------- /config/address/address.go: -------------------------------------------------------------------------------- 1 | package address 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "path" 7 | "strconv" 8 | "strings" 9 | "sync" 10 | 11 | "github.com/toolkits/pkg/file" 12 | "github.com/toolkits/pkg/runner" 13 | ) 14 | 15 | type Module struct { 16 | HTTP string `yaml:"http"` 17 | RPC string `yaml:"rpc"` 18 | Addresses []string `yaml:"addresses"` 19 | } 20 | 21 | var ( 22 | lock sync.Once 23 | mods map[string]Module 24 | ) 25 | 26 | func GetHTTPListen(mod string) string { 27 | return getMod(mod).HTTP 28 | } 29 | 30 | func GetHTTPPort(mod string) int { 31 | return convPort(mod, getMod(mod).HTTP, "http") 32 | } 33 | 34 | func GetRPCListen(mod string) string { 35 | return getMod(mod).RPC 36 | } 37 | 38 | func GetRPCPort(mod string) int { 39 | return convPort(mod, getMod(mod).RPC, "rpc") 40 | } 41 | 42 | func convPort(module, listen, portType string) int { 43 | splitChar := ":" 44 | if IsIPv6(listen) { 45 | splitChar = "]:" 46 | } 47 | port, err := strconv.Atoi(strings.Split(listen, splitChar)[1]) 48 | if err != nil { 49 | fmt.Printf("%s.%s invalid", module, portType) 50 | os.Exit(1) 51 | } 52 | 53 | return port 54 | } 55 | 56 | func GetHTTPAddresses(mod string) []string { 57 | modConf := getMod(mod) 58 | 59 | count := len(modConf.Addresses) 60 | if count == 0 { 61 | return []string{} 62 | } 63 | 64 | port := convPort(mod, modConf.HTTP, "http") 65 | 66 | addresses := make([]string, count) 67 | for i := 0; i < count; i++ { 68 | addresses[i] = fmt.Sprintf("%s:%d", modConf.Addresses[i], port) 69 | } 70 | 71 | return addresses 72 | } 73 | 74 | func GetAddresses(mod string) []string { 75 | modConf := getMod(mod) 76 | return modConf.Addresses 77 | } 78 | 79 | func GetRPCAddresses(mod string) []string { 80 | modConf := getMod(mod) 81 | 82 | count := len(modConf.Addresses) 83 | if count == 0 { 84 | return []string{} 85 | } 86 | 87 | port := convPort(mod, modConf.RPC, "rpc") 88 | 89 | addresses := make([]string, count) 90 | for i := 0; i < count; i++ { 91 | addresses[i] = fmt.Sprintf("%s:%d", modConf.Addresses[i], port) 92 | } 93 | 94 | return addresses 95 | } 96 | 97 | func getMod(modKey string) Module { 98 | lock.Do(func() { 99 | parseConf() 100 | }) 101 | 102 | mod, has := mods[modKey] 103 | if !has { 104 | fmt.Printf("module(%s) configuration section not found", modKey) 105 | os.Exit(1) 106 | } 107 | 108 | return mod 109 | } 110 | 111 | func parseConf() { 112 | conf := getConf() 113 | 114 | var c map[string]Module 115 | err := file.ReadYaml(conf, &c) 116 | if err != nil { 117 | fmt.Println("cannot parse file:", conf) 118 | os.Exit(1) 119 | } 120 | 121 | mods = c 122 | } 123 | 124 | func getConf() string { 125 | conf := path.Join(runner.Cwd, "etc", "address.local.yml") 126 | if file.IsExist(conf) { 127 | return conf 128 | } 129 | 130 | conf = path.Join(runner.Cwd, "etc", "address.yml") 131 | if file.IsExist(conf) { 132 | return conf 133 | } 134 | 135 | fmt.Println("configuration file address.[local.]yml not found") 136 | os.Exit(1) 137 | return "" 138 | } 139 | 140 | func IsIPv6(address string) bool { 141 | return strings.Count(address, ":") >= 2 142 | } 143 | -------------------------------------------------------------------------------- /config/config.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "sync" 7 | 8 | "github.com/spf13/viper" 9 | "github.com/toolkits/pkg/file" 10 | ) 11 | 12 | type ConfYaml struct { 13 | Logger LoggerSection `yaml:"logger"` 14 | Probe probeSection `yaml:"probe"` 15 | Ping map[string][]string `yaml:"ping"` 16 | Url map[string][]string `yaml:"url"` 17 | Server serverSection `yaml:"server"` 18 | } 19 | 20 | type serverSection struct { 21 | RpcMethod string `yaml:"rpcMethod"` 22 | } 23 | 24 | type probeSection struct { 25 | Region string `yaml:"region"` 26 | Timeout int64 `yaml:"timeout"` 27 | Limit int64 `yaml:"limit"` 28 | Interval int `yaml:"interval"` 29 | Headers map[string]string `yaml:"headers"` 30 | } 31 | 32 | var ( 33 | Config *ConfYaml 34 | lock = new(sync.RWMutex) 35 | Endpoint string 36 | Cwd string 37 | ) 38 | 39 | // Get configuration file 40 | func Get() *ConfYaml { 41 | lock.RLock() 42 | defer lock.RUnlock() 43 | return Config 44 | } 45 | 46 | func Parse(conf string) error { 47 | bs, err := file.ReadBytes(conf) 48 | if err != nil { 49 | return fmt.Errorf("cannot read yml[%s]: %v", conf, err) 50 | } 51 | 52 | lock.Lock() 53 | defer lock.Unlock() 54 | 55 | viper.SetConfigType("yaml") 56 | err = viper.ReadConfig(bytes.NewBuffer(bs)) 57 | if err != nil { 58 | return fmt.Errorf("cannot read yml[%s]: %v", conf, err) 59 | } 60 | 61 | viper.SetDefault("server.rpcMethod", "Transfer.Push") 62 | 63 | err = viper.Unmarshal(&Config) 64 | if err != nil { 65 | return fmt.Errorf("Unmarshal %v", err) 66 | } 67 | 68 | return nil 69 | } 70 | -------------------------------------------------------------------------------- /config/const.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | const Version = "0.3.0" 4 | -------------------------------------------------------------------------------- /config/logger.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | 7 | "github.com/toolkits/pkg/logger" 8 | ) 9 | 10 | type LoggerSection struct { 11 | Dir string `json:"dir"` 12 | Level string `json:"level"` 13 | KeepHours uint `json:"keepHours"` 14 | } 15 | 16 | func InitLog(l LoggerSection) { 17 | 18 | lb, err := logger.NewFileBackend(l.Dir) 19 | if err != nil { 20 | fmt.Println("cannot init logger:", err) 21 | os.Exit(1) 22 | } 23 | lb.SetRotateByHour(true) 24 | lb.SetKeepHours(l.KeepHours) 25 | 26 | logger.SetLogging(l.Level, lb) 27 | } 28 | -------------------------------------------------------------------------------- /control: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # release version 4 | 5 | CWD=$(cd $(dirname $0)/; pwd) 6 | cd $CWD 7 | 8 | start() 9 | { 10 | mod="probe" 11 | 12 | binfile=n9e-${mod} 13 | 14 | if [ ! -f $binfile ]; then 15 | echo "file[$binfile] not found" 16 | exit 1 17 | fi 18 | 19 | if [ $(ps aux|grep -v grep|grep -v control|grep "$binfile" -c) -gt 0 ]; then 20 | echo "${mod} already started" 21 | return 22 | fi 23 | 24 | mkdir -p logs/ 25 | nohup $CWD/$binfile &> logs/stdout.log & 26 | 27 | for((i=1;i<=15;i++)); do 28 | if [ $(ps aux|grep -v grep|grep -v control|grep "$binfile" -c) -gt 0 ]; then 29 | echo "${mod} started" 30 | return 31 | fi 32 | sleep 0.2 33 | done 34 | 35 | echo "cannot start ${mod}" 36 | exit 1 37 | } 38 | 39 | stop() 40 | { 41 | mod="probe" 42 | 43 | binfile=n9e-${mod} 44 | 45 | if [ $(ps aux|grep -v grep|grep -v control|grep "$binfile" -c) -eq 0 ]; then 46 | echo "${mod} already stopped" 47 | return 48 | fi 49 | 50 | ps aux|grep -v grep|grep -v control|grep "$binfile"|awk '{print $2}'|xargs kill 51 | for((i=1;i<=15;i++)); do 52 | if [ $(ps aux|grep -v grep|grep -v control|grep "$binfile" -c) -eq 0 ]; then 53 | echo "${mod} stopped" 54 | return 55 | fi 56 | sleep 0.2 57 | done 58 | 59 | echo "cannot stop $mod" 60 | exit 1 61 | } 62 | 63 | restart() 64 | { 65 | mod="probe" 66 | stop $mod 67 | start $mod 68 | 69 | status 70 | } 71 | 72 | status() 73 | { 74 | ps aux|grep -v grep|grep "probe" 75 | } 76 | 77 | build() 78 | { 79 | go build 80 | if [ $? -ne 0 ]; then 81 | exit $? 82 | fi 83 | mod="probe" 84 | 85 | binfile=n9e-${mod} 86 | ./$binfile -v 87 | } 88 | 89 | pack() 90 | { 91 | build 92 | git log -1 --pretty=%h > gitversion 93 | mod="probe" 94 | binfile=n9e-${mod} 95 | version=`./$binfile -v | cut -d " " -f2` 96 | file_list="control install.sh etc service $binfile" 97 | echo "...tar $binfile-$version.tar.gz <= $file_list" 98 | tar zcf $binfile-$version.tar.gz gitversion $file_list 99 | } 100 | 101 | case "$1" in 102 | start) 103 | start $2 104 | ;; 105 | stop) 106 | stop $2 107 | ;; 108 | restart) 109 | restart $2 110 | ;; 111 | status) 112 | status 113 | ;; 114 | build) 115 | build 116 | ;; 117 | pack) 118 | pack 119 | ;; 120 | *) 121 | usage 122 | esac 123 | -------------------------------------------------------------------------------- /etc/address.yml: -------------------------------------------------------------------------------- 1 | --- 2 | transfer: 3 | http: 0.0.0.0:8008 4 | rpc: 0.0.0.0:8009 5 | addresses: 6 | - 192.168.0.100 7 | 8 | probe: 9 | http: 127.0.0.1:2059 -------------------------------------------------------------------------------- /etc/probe.yml: -------------------------------------------------------------------------------- 1 | logger: 2 | dir: logs/ 3 | level: INFO 4 | keepHours: 24 5 | 6 | probe: 7 | # 如果需要区分来自不同区域的探针,可以通过在配置 region 来插入 tag 8 | #region: default 9 | timeout: 5 # 探测的超时时间,单位是秒 10 | limit: 10 # 并发限制 11 | interval: 30 # 请求的间隔 12 | headers: # 插入到 http 请求中的 headers,可以多条 13 | user-agent: Mozilla/5.0 (Linux; Android 6.0.1; Moto G (4)) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Mobile Safari/537.36 Edg/87.0.664.66 14 | 15 | # server: 16 | # rpcMethod: "Transfer.Push" 17 | 18 | ping: 19 | 107: # n9e 节点上的 nid 号 20 | - 114.114.114.114 # 要探测的 ip 地址列表 21 | - 114.114.115.115 22 | 23 | url: 24 | 107: # n9e 节点上的 nid 号 25 | - https://www.baidu.com # 要探测的 ip 地址列表 26 | - https://www.sjtu.edu.cn/ 27 | - https://bbs.ngacn.cc 28 | - https://www.163.com -------------------------------------------------------------------------------- /gitversion: -------------------------------------------------------------------------------- 1 | d6bb045 2 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/shanghai-edu/n9e-probe 2 | 3 | go 1.15 4 | 5 | require ( 6 | github.com/gin-contrib/pprof v1.3.0 7 | github.com/gin-gonic/gin v1.6.3 8 | github.com/mattn/go-isatty v0.0.12 9 | github.com/paulstuart/ping v0.0.0-20140925212352-0345a9703e43 10 | github.com/spf13/viper v1.7.1 11 | github.com/toolkits/pkg v1.1.3 12 | github.com/ugorji/go/codec v1.2.2 13 | go.uber.org/automaxprocs v1.3.0 // indirect 14 | gopkg.in/yaml.v2 v2.4.0 // indirect 15 | ) 16 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= 2 | cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= 3 | cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= 4 | cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= 5 | cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= 6 | cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= 7 | cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= 8 | cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= 9 | cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= 10 | cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk= 11 | cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= 12 | cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= 13 | dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= 14 | github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= 15 | github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= 16 | github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= 17 | github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= 18 | github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= 19 | github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= 20 | github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= 21 | github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= 22 | github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= 23 | github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= 24 | github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= 25 | github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84= 26 | github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= 27 | github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= 28 | github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= 29 | github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= 30 | github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= 31 | github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= 32 | github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= 33 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 34 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 35 | github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= 36 | github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= 37 | github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= 38 | github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I= 39 | github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= 40 | github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= 41 | github.com/gin-contrib/pprof v1.3.0 h1:G9eK6HnbkSqDZBYbzG4wrjCsA4e+cvYAHUZw6W+W9K0= 42 | github.com/gin-contrib/pprof v1.3.0/go.mod h1:waMjT1H9b179t3CxuG1cV3DHpga6ybizwfBaM5OXaB0= 43 | github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= 44 | github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= 45 | github.com/gin-gonic/gin v1.6.2/go.mod h1:75u5sXoLsGZoRN5Sgbi1eraJ4GU3++wFwWzhwvtwp4M= 46 | github.com/gin-gonic/gin v1.6.3 h1:ahKqKTFpO5KTPHxWZjEdPScmYaGtLo8Y4DMHoEsnp14= 47 | github.com/gin-gonic/gin v1.6.3/go.mod h1:75u5sXoLsGZoRN5Sgbi1eraJ4GU3++wFwWzhwvtwp4M= 48 | github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= 49 | github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= 50 | github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= 51 | github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= 52 | github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= 53 | github.com/go-playground/locales v0.13.0 h1:HyWk6mgj5qFqCT5fjGBuRArbVDfE4hi8+e8ceBS/t7Q= 54 | github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8= 55 | github.com/go-playground/universal-translator v0.17.0 h1:icxd5fm+REJzpZx7ZfpaD876Lmtgy7VtROAbHHXk8no= 56 | github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA= 57 | github.com/go-playground/validator/v10 v10.2.0 h1:KgJ0snyC2R9VXYN2rneOtQcw5aHQB1Vv0sFl1UcHBOY= 58 | github.com/go-playground/validator/v10 v10.2.0/go.mod h1:uOYAAleCW8F/7oMFd6aG0GOhaH6EGOAJShg8Id5JGkI= 59 | github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= 60 | github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= 61 | github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= 62 | github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= 63 | github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= 64 | github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= 65 | github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= 66 | github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= 67 | github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 68 | github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 69 | github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 70 | github.com/golang/protobuf v1.3.3 h1:gyjaxf+svBWX08ZjK86iN9geUJF0H6gp2IRKX6Nf6/I= 71 | github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= 72 | github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= 73 | github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= 74 | github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= 75 | github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 76 | github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= 77 | github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= 78 | github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= 79 | github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= 80 | github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= 81 | github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= 82 | github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= 83 | github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= 84 | github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= 85 | github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= 86 | github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= 87 | github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= 88 | github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q= 89 | github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= 90 | github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= 91 | github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= 92 | github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= 93 | github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= 94 | github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= 95 | github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU= 96 | github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU= 97 | github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= 98 | github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= 99 | github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= 100 | github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90= 101 | github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= 102 | github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= 103 | github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= 104 | github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= 105 | github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= 106 | github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ= 107 | github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= 108 | github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= 109 | github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= 110 | github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= 111 | github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= 112 | github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= 113 | github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= 114 | github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= 115 | github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= 116 | github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= 117 | github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= 118 | github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= 119 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= 120 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 121 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 122 | github.com/leodido/go-urn v1.2.0 h1:hpXL4XnriNwQ/ABnpepYM/1vCLWNDfUNts8dX3xTG6Y= 123 | github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII= 124 | github.com/magiconair/properties v1.8.1 h1:ZC2Vc7/ZFkGmsVC9KvOjumD+G5lXy2RtTKyzRKO2BQ4= 125 | github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= 126 | github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= 127 | github.com/mattn/go-isatty v0.0.3 h1:ns/ykhmWi7G9O+8a448SecJU3nSMBXJfqQkl0upE1jI= 128 | github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= 129 | github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY= 130 | github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= 131 | github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= 132 | github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= 133 | github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= 134 | github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= 135 | github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= 136 | github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg= 137 | github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY= 138 | github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= 139 | github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE= 140 | github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= 141 | github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 142 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 143 | github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= 144 | github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= 145 | github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= 146 | github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= 147 | github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= 148 | github.com/paulstuart/ping v0.0.0-20140925212352-0345a9703e43 h1:KdVgTa60PIJyle4rgRiRDMLU4rBPlvP9/Mig8tka7Ow= 149 | github.com/paulstuart/ping v0.0.0-20140925212352-0345a9703e43/go.mod h1:Mqu1lFC2j84abm0x+RLoyj/uWj4iuO6mI/yabMSjX6Q= 150 | github.com/pelletier/go-toml v1.2.0 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181zc= 151 | github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= 152 | github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 153 | github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 154 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 155 | github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= 156 | github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= 157 | github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso= 158 | github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= 159 | github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= 160 | github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= 161 | github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= 162 | github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= 163 | github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= 164 | github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= 165 | github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= 166 | github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= 167 | github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= 168 | github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= 169 | github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= 170 | github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= 171 | github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= 172 | github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= 173 | github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= 174 | github.com/spf13/afero v1.1.2 h1:m8/z1t7/fwjysjQRYbP0RD+bUIF/8tJwPdEZsI83ACI= 175 | github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= 176 | github.com/spf13/cast v1.3.0 h1:oget//CVOEoFewqQxwr0Ej5yjygnqGkvggSE/gB35Q8= 177 | github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= 178 | github.com/spf13/jwalterweatherman v1.0.0 h1:XHEdyB+EcvlqZamSM4ZOMGlc93t6AcsBEu9Gc1vn7yk= 179 | github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= 180 | github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg= 181 | github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= 182 | github.com/spf13/viper v1.7.1 h1:pM5oEahlgWv/WnHXpgbKz7iLIxRf65tye2Ci+XFK5sk= 183 | github.com/spf13/viper v1.7.1/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg= 184 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 185 | github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 186 | github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= 187 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 188 | github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= 189 | github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s= 190 | github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= 191 | github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= 192 | github.com/toolkits/pkg v1.1.3 h1:cjZMz9hmuTv4v7ivYERA9mWJCLKyr8JMd4S+CL/YzMM= 193 | github.com/toolkits/pkg v1.1.3/go.mod h1:ge83E8FQqUnFk+2wtVtZ8kvbmoSjE1l8FP3f+qmR0fY= 194 | github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw= 195 | github.com/ugorji/go v1.2.2 h1:60ZHIOcsJlo3bJm9CbTVu7OSqT2mxaEmyQbK2NwCkn0= 196 | github.com/ugorji/go v1.2.2/go.mod h1:bitgyERdV7L7Db/Z5gfd5v2NQMNhhiFiZwpgMw2SP7k= 197 | github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY= 198 | github.com/ugorji/go/codec v1.2.2 h1:08Gah8d+dXj4cZNUHhtuD/S4PXD5WpVbj5B8/ClELAQ= 199 | github.com/ugorji/go/codec v1.2.2/go.mod h1:OM8g7OAy52uYl3Yk+RE/3AS1nXFn1Wh4PPLtupCxbuU= 200 | github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= 201 | go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= 202 | go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= 203 | go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= 204 | go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= 205 | go.uber.org/automaxprocs v1.3.0 h1:II28aZoGdaglS5vVNnspf28lnZpXScxtIozx1lAjdb0= 206 | go.uber.org/automaxprocs v1.3.0/go.mod h1:9CWT6lKIep8U41DDaPiH6eFscnTyjfTANNQNx6LrIcA= 207 | go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= 208 | go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= 209 | golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= 210 | golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= 211 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 212 | golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 213 | golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 214 | golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= 215 | golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= 216 | golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= 217 | golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= 218 | golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= 219 | golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= 220 | golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= 221 | golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= 222 | golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= 223 | golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= 224 | golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 225 | golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 226 | golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 227 | golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 228 | golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= 229 | golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= 230 | golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= 231 | golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= 232 | golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= 233 | golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 234 | golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 235 | golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 236 | golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 237 | golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 238 | golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 239 | golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 240 | golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 241 | golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 242 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 243 | golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 244 | golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 245 | golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= 246 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 247 | golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= 248 | golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= 249 | golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= 250 | golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 251 | golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 252 | golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 253 | golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 254 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 255 | golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 256 | golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 257 | golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 258 | golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 259 | golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 260 | golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 261 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 262 | golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 263 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 264 | golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 265 | golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 266 | golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 267 | golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 268 | golang.org/x/sys v0.0.0-20200116001909-b77594299b42 h1:vEOn+mP2zCOVzKckCZy6YsCtDblrpj/w7B9nxGNELpg= 269 | golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 270 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 271 | golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 272 | golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= 273 | golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= 274 | golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= 275 | golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= 276 | golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 277 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 278 | golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 279 | golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= 280 | golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 281 | golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 282 | golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 283 | golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 284 | golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= 285 | golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= 286 | golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= 287 | golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= 288 | golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= 289 | golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 290 | golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 291 | golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 292 | golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 293 | golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 294 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 295 | google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= 296 | google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= 297 | google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= 298 | google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= 299 | google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= 300 | google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= 301 | google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= 302 | google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= 303 | google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= 304 | google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= 305 | google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= 306 | google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= 307 | google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= 308 | google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= 309 | google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= 310 | google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= 311 | google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= 312 | google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= 313 | google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= 314 | google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= 315 | google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= 316 | gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= 317 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 318 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 319 | gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= 320 | gopkg.in/ini.v1 v1.51.0 h1:AQvPpx3LzTDM0AjnIRlVFwFFGC+npRopjZxLJj6gdno= 321 | gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= 322 | gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= 323 | gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= 324 | gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 325 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 326 | gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 327 | gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 328 | gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= 329 | gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= 330 | honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 331 | honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 332 | honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 333 | honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= 334 | rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= 335 | -------------------------------------------------------------------------------- /http/health.go: -------------------------------------------------------------------------------- 1 | package http 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | 7 | "github.com/gin-gonic/gin" 8 | ) 9 | 10 | func ping(c *gin.Context) { 11 | c.String(200, "pong") 12 | } 13 | 14 | func addr(c *gin.Context) { 15 | c.String(200, c.Request.RemoteAddr) 16 | } 17 | 18 | func pid(c *gin.Context) { 19 | c.String(200, fmt.Sprintf("%d", os.Getpid())) 20 | } 21 | -------------------------------------------------------------------------------- /http/middleware/logger.go: -------------------------------------------------------------------------------- 1 | package middleware 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "io" 7 | "io/ioutil" 8 | "net/http" 9 | "os" 10 | "time" 11 | 12 | "github.com/gin-gonic/gin" 13 | "github.com/mattn/go-isatty" 14 | "github.com/toolkits/pkg/logger" 15 | ) 16 | 17 | type consoleColorModeValue int 18 | 19 | const ( 20 | autoColor consoleColorModeValue = iota 21 | disableColor 22 | forceColor 23 | ) 24 | 25 | var ( 26 | green = string([]byte{27, 91, 57, 55, 59, 52, 50, 109}) 27 | white = string([]byte{27, 91, 57, 48, 59, 52, 55, 109}) 28 | yellow = string([]byte{27, 91, 57, 48, 59, 52, 51, 109}) 29 | red = string([]byte{27, 91, 57, 55, 59, 52, 49, 109}) 30 | blue = string([]byte{27, 91, 57, 55, 59, 52, 52, 109}) 31 | magenta = string([]byte{27, 91, 57, 55, 59, 52, 53, 109}) 32 | cyan = string([]byte{27, 91, 57, 55, 59, 52, 54, 109}) 33 | reset = string([]byte{27, 91, 48, 109}) 34 | consoleColorMode = autoColor 35 | ) 36 | 37 | // LoggerConfig defines the config for Logger middleware. 38 | type LoggerConfig struct { 39 | // Optional. Default value is gin.defaultLogFormatter 40 | Formatter LogFormatter 41 | 42 | // Output is a writer where logs are written. 43 | // Optional. Default value is gin.DefaultWriter. 44 | Output io.Writer 45 | 46 | // SkipPaths is a url path array which logs are not written. 47 | // Optional. 48 | SkipPaths []string 49 | } 50 | 51 | // LogFormatter gives the signature of the formatter function passed to LoggerWithFormatter 52 | type LogFormatter func(params LogFormatterParams) string 53 | 54 | // LogFormatterParams is the structure any formatter will be handed when time to log comes 55 | type LogFormatterParams struct { 56 | Request *http.Request 57 | 58 | // TimeStamp shows the time after the server returns a response. 59 | TimeStamp time.Time 60 | // StatusCode is HTTP response code. 61 | StatusCode int 62 | // Latency is how much time the server cost to process a certain request. 63 | Latency time.Duration 64 | // ClientIP equals Context's ClientIP method. 65 | ClientIP string 66 | // Method is the HTTP method given to the request. 67 | Method string 68 | // Path is a path the client requests. 69 | Path string 70 | // ErrorMessage is set if error has occurred in processing the request. 71 | ErrorMessage string 72 | // isTerm shows whether does gin's output descriptor refers to a terminal. 73 | isTerm bool 74 | // BodySize is the size of the Response Body 75 | BodySize int 76 | // Keys are the keys set on the request's context. 77 | Keys map[string]interface{} 78 | } 79 | 80 | // StatusCodeColor is the ANSI color for appropriately logging http status code to a terminal. 81 | func (p *LogFormatterParams) StatusCodeColor() string { 82 | code := p.StatusCode 83 | 84 | switch { 85 | case code >= http.StatusOK && code < http.StatusMultipleChoices: 86 | return green 87 | case code >= http.StatusMultipleChoices && code < http.StatusBadRequest: 88 | return white 89 | case code >= http.StatusBadRequest && code < http.StatusInternalServerError: 90 | return yellow 91 | default: 92 | return red 93 | } 94 | } 95 | 96 | // MethodColor is the ANSI color for appropriately logging http method to a terminal. 97 | func (p *LogFormatterParams) MethodColor() string { 98 | method := p.Method 99 | 100 | switch method { 101 | case "GET": 102 | return blue 103 | case "POST": 104 | return cyan 105 | case "PUT": 106 | return yellow 107 | case "DELETE": 108 | return red 109 | case "PATCH": 110 | return green 111 | case "HEAD": 112 | return magenta 113 | case "OPTIONS": 114 | return white 115 | default: 116 | return reset 117 | } 118 | } 119 | 120 | // ResetColor resets all escape attributes. 121 | func (p *LogFormatterParams) ResetColor() string { 122 | return reset 123 | } 124 | 125 | // IsOutputColor indicates whether can colors be outputted to the log. 126 | func (p *LogFormatterParams) IsOutputColor() bool { 127 | return consoleColorMode == forceColor || (consoleColorMode == autoColor && p.isTerm) 128 | } 129 | 130 | // defaultLogFormatter is the default log format function Logger middleware uses. 131 | var defaultLogFormatter = func(param LogFormatterParams) string { 132 | var statusColor, methodColor, resetColor string 133 | if param.IsOutputColor() { 134 | statusColor = param.StatusCodeColor() 135 | methodColor = param.MethodColor() 136 | resetColor = param.ResetColor() 137 | } 138 | 139 | if param.Latency > time.Minute { 140 | // Truncate in a golang < 1.8 safe way 141 | param.Latency = param.Latency - param.Latency%time.Second 142 | } 143 | return fmt.Sprintf("[GIN] |%s %3d %s| %13v | %15s |%s %-7s %s %s\n%s", 144 | statusColor, param.StatusCode, resetColor, 145 | param.Latency, 146 | param.ClientIP, 147 | methodColor, param.Method, resetColor, 148 | param.Path, 149 | param.ErrorMessage, 150 | ) 151 | } 152 | 153 | // DisableConsoleColor disables color output in the console. 154 | func DisableConsoleColor() { 155 | consoleColorMode = disableColor 156 | } 157 | 158 | // ForceConsoleColor force color output in the console. 159 | func ForceConsoleColor() { 160 | consoleColorMode = forceColor 161 | } 162 | 163 | // ErrorLogger returns a handlerfunc for any error type. 164 | func ErrorLogger() gin.HandlerFunc { 165 | return ErrorLoggerT(gin.ErrorTypeAny) 166 | } 167 | 168 | // ErrorLoggerT returns a handlerfunc for a given error type. 169 | func ErrorLoggerT(typ gin.ErrorType) gin.HandlerFunc { 170 | return func(c *gin.Context) { 171 | c.Next() 172 | errors := c.Errors.ByType(typ) 173 | if len(errors) > 0 { 174 | c.JSON(-1, errors) 175 | } 176 | } 177 | } 178 | 179 | // Logger instances a Logger middleware that will write the logs to gin.DefaultWriter. 180 | // By default gin.DefaultWriter = os.Stdout. 181 | func Logger() gin.HandlerFunc { 182 | return LoggerWithConfig(LoggerConfig{}) 183 | } 184 | 185 | // LoggerWithFormatter instance a Logger middleware with the specified log format function. 186 | func LoggerWithFormatter(f LogFormatter) gin.HandlerFunc { 187 | return LoggerWithConfig(LoggerConfig{ 188 | Formatter: f, 189 | }) 190 | } 191 | 192 | // LoggerWithWriter instance a Logger middleware with the specified writer buffer. 193 | // Example: os.Stdout, a file opened in write mode, a socket... 194 | func LoggerWithWriter(out io.Writer, notlogged ...string) gin.HandlerFunc { 195 | return LoggerWithConfig(LoggerConfig{ 196 | Output: out, 197 | SkipPaths: notlogged, 198 | }) 199 | } 200 | 201 | // LoggerWithConfig instance a Logger middleware with config. 202 | func LoggerWithConfig(conf LoggerConfig) gin.HandlerFunc { 203 | formatter := conf.Formatter 204 | if formatter == nil { 205 | formatter = defaultLogFormatter 206 | } 207 | 208 | out := conf.Output 209 | if out == nil { 210 | out = os.Stdout 211 | } 212 | 213 | notlogged := conf.SkipPaths 214 | 215 | isTerm := true 216 | 217 | if w, ok := out.(*os.File); !ok || os.Getenv("TERM") == "dumb" || 218 | (!isatty.IsTerminal(w.Fd()) && !isatty.IsCygwinTerminal(w.Fd())) { 219 | isTerm = false 220 | } 221 | 222 | var skip map[string]struct{} 223 | 224 | if length := len(notlogged); length > 0 { 225 | skip = make(map[string]struct{}, length) 226 | 227 | for _, path := range notlogged { 228 | skip[path] = struct{}{} 229 | } 230 | } 231 | 232 | return func(c *gin.Context) { 233 | // Start timer 234 | start := time.Now() 235 | path := c.Request.URL.Path 236 | raw := c.Request.URL.RawQuery 237 | 238 | var ( 239 | rdr1 io.ReadCloser 240 | rdr2 io.ReadCloser 241 | ) 242 | 243 | if c.Request.Method != "GET" { 244 | buf, _ := ioutil.ReadAll(c.Request.Body) 245 | rdr1 = ioutil.NopCloser(bytes.NewBuffer(buf)) 246 | rdr2 = ioutil.NopCloser(bytes.NewBuffer(buf)) 247 | 248 | c.Request.Body = rdr2 249 | } 250 | 251 | // Process request 252 | c.Next() 253 | 254 | // Log only when path is not being skipped 255 | if _, ok := skip[path]; !ok { 256 | param := LogFormatterParams{ 257 | Request: c.Request, 258 | isTerm: isTerm, 259 | Keys: c.Keys, 260 | } 261 | 262 | // Stop timer 263 | param.TimeStamp = time.Now() 264 | param.Latency = param.TimeStamp.Sub(start) 265 | 266 | param.ClientIP = c.ClientIP() 267 | param.Method = c.Request.Method 268 | param.StatusCode = c.Writer.Status() 269 | param.ErrorMessage = c.Errors.ByType(gin.ErrorTypePrivate).String() 270 | 271 | param.BodySize = c.Writer.Size() 272 | 273 | if raw != "" { 274 | path = path + "?" + raw 275 | } 276 | 277 | param.Path = path 278 | 279 | // fmt.Fprint(out, formatter(param)) 280 | logger.Info(formatter(param)) 281 | 282 | if c.Request.Method != "GET" { 283 | logger.Info(readBody(rdr1)) 284 | } 285 | } 286 | } 287 | } 288 | 289 | func readBody(reader io.Reader) string { 290 | buf := new(bytes.Buffer) 291 | buf.ReadFrom(reader) 292 | 293 | s := buf.String() 294 | return s 295 | } 296 | -------------------------------------------------------------------------------- /http/middleware/recovery.go: -------------------------------------------------------------------------------- 1 | package middleware 2 | 3 | // Copyright 2014 Manu Martinez-Almeida. All rights reserved. 4 | // Use of this source code is governed by a MIT style 5 | // license that can be found in the LICENSE file. 6 | 7 | import ( 8 | "bytes" 9 | "fmt" 10 | "io" 11 | "io/ioutil" 12 | "log" 13 | "net" 14 | "net/http" 15 | "net/http/httputil" 16 | "os" 17 | "runtime" 18 | "strings" 19 | "time" 20 | 21 | "github.com/gin-gonic/gin" 22 | "github.com/toolkits/pkg/errors" 23 | ) 24 | 25 | var ( 26 | dunno = []byte("???") 27 | centerDot = []byte("·") 28 | dot = []byte(".") 29 | slash = []byte("/") 30 | ) 31 | 32 | // Recovery returns a middleware that recovers from any panics and writes a 500 if there was one. 33 | func Recovery() gin.HandlerFunc { 34 | return RecoveryWithWriter(gin.DefaultErrorWriter) 35 | } 36 | 37 | // RecoveryWithWriter returns a middleware for a given writer that recovers from any panics and writes a 500 if there was one. 38 | func RecoveryWithWriter(out io.Writer) gin.HandlerFunc { 39 | var logger *log.Logger 40 | if out != nil { 41 | logger = log.New(out, "\n\n\x1b[31m", log.LstdFlags) 42 | } 43 | return func(c *gin.Context) { 44 | defer func() { 45 | if err := recover(); err != nil { 46 | // custom error 47 | if e, ok := err.(errors.PageError); ok { 48 | c.JSON(200, gin.H{"err": e.Message}) 49 | c.Abort() 50 | return 51 | } 52 | 53 | // Check for a broken connection, as it is not really a 54 | // condition that warrants a panic stack trace. 55 | var brokenPipe bool 56 | if ne, ok := err.(*net.OpError); ok { 57 | if se, ok := ne.Err.(*os.SyscallError); ok { 58 | if strings.Contains(strings.ToLower(se.Error()), "broken pipe") || strings.Contains(strings.ToLower(se.Error()), "connection reset by peer") { 59 | brokenPipe = true 60 | } 61 | } 62 | } 63 | if logger != nil { 64 | stack := stack(3) 65 | httpRequest, _ := httputil.DumpRequest(c.Request, false) 66 | headers := strings.Split(string(httpRequest), "\r\n") 67 | for idx, header := range headers { 68 | current := strings.Split(header, ":") 69 | if current[0] == "Authorization" { 70 | headers[idx] = current[0] + ": *" 71 | } 72 | } 73 | if brokenPipe { 74 | logger.Printf("%s\n%s%s", err, string(httpRequest), reset) 75 | } else if gin.IsDebugging() { 76 | logger.Printf("[Recovery] %s panic recovered:\n%s\n%s\n%s%s", 77 | timeFormat(time.Now()), strings.Join(headers, "\r\n"), err, stack, reset) 78 | } else { 79 | logger.Printf("[Recovery] %s panic recovered:\n%s\n%s%s", 80 | timeFormat(time.Now()), err, stack, reset) 81 | } 82 | } 83 | 84 | // If the connection is dead, we can't write a status to it. 85 | if brokenPipe { 86 | c.Error(err.(error)) // nolint: errcheck 87 | c.Abort() 88 | } else { 89 | c.AbortWithStatus(http.StatusInternalServerError) 90 | } 91 | } 92 | }() 93 | c.Next() 94 | } 95 | } 96 | 97 | // stack returns a nicely formatted stack frame, skipping skip frames. 98 | func stack(skip int) []byte { 99 | buf := new(bytes.Buffer) // the returned data 100 | // As we loop, we open files and read them. These variables record the currently 101 | // loaded file. 102 | var lines [][]byte 103 | var lastFile string 104 | for i := skip; ; i++ { // Skip the expected number of frames 105 | pc, file, line, ok := runtime.Caller(i) 106 | if !ok { 107 | break 108 | } 109 | // Print this much at least. If we can't find the source, it won't show. 110 | fmt.Fprintf(buf, "%s:%d (0x%x)\n", file, line, pc) 111 | if file != lastFile { 112 | data, err := ioutil.ReadFile(file) 113 | if err != nil { 114 | continue 115 | } 116 | lines = bytes.Split(data, []byte{'\n'}) 117 | lastFile = file 118 | } 119 | fmt.Fprintf(buf, "\t%s: %s\n", function(pc), source(lines, line)) 120 | } 121 | return buf.Bytes() 122 | } 123 | 124 | // source returns a space-trimmed slice of the n'th line. 125 | func source(lines [][]byte, n int) []byte { 126 | n-- // in stack trace, lines are 1-indexed but our array is 0-indexed 127 | if n < 0 || n >= len(lines) { 128 | return dunno 129 | } 130 | return bytes.TrimSpace(lines[n]) 131 | } 132 | 133 | // function returns, if possible, the name of the function containing the PC. 134 | func function(pc uintptr) []byte { 135 | fn := runtime.FuncForPC(pc) 136 | if fn == nil { 137 | return dunno 138 | } 139 | name := []byte(fn.Name()) 140 | // The name includes the path name to the package, which is unnecessary 141 | // since the file name is already included. Plus, it has center dots. 142 | // That is, we see 143 | // runtime/debug.*T·ptrmethod 144 | // and want 145 | // *T.ptrmethod 146 | // Also the package path might contains dot (e.g. code.google.com/...), 147 | // so first eliminate the path prefix 148 | if lastSlash := bytes.LastIndex(name, slash); lastSlash >= 0 { 149 | name = name[lastSlash+1:] 150 | } 151 | if period := bytes.Index(name, dot); period >= 0 { 152 | name = name[period+1:] 153 | } 154 | name = bytes.Replace(name, centerDot, dot, -1) 155 | return name 156 | } 157 | 158 | func timeFormat(t time.Time) string { 159 | return t.Format("2006/01/02 - 15:04:05") 160 | } 161 | -------------------------------------------------------------------------------- /http/route.go: -------------------------------------------------------------------------------- 1 | package http 2 | 3 | import ( 4 | "github.com/gin-contrib/pprof" 5 | "github.com/gin-gonic/gin" 6 | ) 7 | 8 | func Config(r *gin.Engine) { 9 | // collector apis, compatible with open-falcon 10 | v1 := r.Group("/v1") 11 | { 12 | v1.GET("/ping", ping) 13 | v1.GET("/pid", pid) 14 | v1.GET("/addr", addr) 15 | } 16 | 17 | pprof.Register(r, "/debug/pprof") 18 | } 19 | -------------------------------------------------------------------------------- /http/start.go: -------------------------------------------------------------------------------- 1 | package http 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "net/http" 7 | "os" 8 | "time" 9 | 10 | "github.com/gin-gonic/gin" 11 | 12 | "github.com/shanghai-edu/n9e-probe/config/address" 13 | "github.com/shanghai-edu/n9e-probe/http/middleware" 14 | ) 15 | 16 | var srv = &http.Server{ 17 | ReadTimeout: 10 * time.Second, 18 | WriteTimeout: 10 * time.Second, 19 | MaxHeaderBytes: 1 << 20, 20 | } 21 | 22 | func Start() { 23 | recoveryMid := middleware.Recovery() 24 | 25 | gin.SetMode(gin.ReleaseMode) 26 | middleware.DisableConsoleColor() 27 | 28 | r := gin.New() 29 | r.Use(recoveryMid) 30 | 31 | Config(r) 32 | 33 | srv.Addr = address.GetHTTPListen("probe") 34 | srv.Handler = r 35 | 36 | go func() { 37 | fmt.Println("http.listening:", srv.Addr) 38 | if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed { 39 | fmt.Printf("listening %s occur error: %s\n", srv.Addr, err) 40 | os.Exit(3) 41 | } 42 | }() 43 | } 44 | 45 | // Shutdown http server 46 | func Shutdown() { 47 | ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) 48 | defer cancel() 49 | if err := srv.Shutdown(ctx); err != nil { 50 | fmt.Println("cannot shutdown http server:", err) 51 | os.Exit(2) 52 | } 53 | 54 | // catching ctx.Done(). timeout of 5 seconds. 55 | select { 56 | case <-ctx.Done(): 57 | fmt.Println("shutdown http server timeout of 5 seconds.") 58 | default: 59 | fmt.Println("http server stopped") 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /install.sh: -------------------------------------------------------------------------------- 1 | cp service/probe.service /usr/lib/systemd/system/ 2 | systemctl daemon-reload 3 | systemctl start probe 4 | systemctl enable probe 5 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | "os" 7 | "os/signal" 8 | "syscall" 9 | 10 | "github.com/shanghai-edu/n9e-probe/config" 11 | "github.com/shanghai-edu/n9e-probe/http" 12 | "github.com/shanghai-edu/n9e-probe/probe" 13 | "github.com/shanghai-edu/n9e-probe/probe/core" 14 | 15 | "github.com/toolkits/pkg/file" 16 | "github.com/toolkits/pkg/logger" 17 | "github.com/toolkits/pkg/runner" 18 | ) 19 | 20 | var ( 21 | vers *bool 22 | help *bool 23 | conf *string 24 | ) 25 | 26 | func init() { 27 | vers = flag.Bool("v", false, "display the version.") 28 | help = flag.Bool("h", false, "print this help.") 29 | conf = flag.String("f", "", "specify configuration file.") 30 | flag.Parse() 31 | 32 | if *vers { 33 | fmt.Println("version:", config.Version) 34 | os.Exit(0) 35 | } 36 | 37 | if *help { 38 | flag.Usage() 39 | os.Exit(0) 40 | } 41 | } 42 | 43 | func main() { 44 | aconf() 45 | pconf() 46 | start() 47 | 48 | cfg := config.Get() 49 | config.InitLog(cfg.Logger) 50 | 51 | core.InitRpcClients() 52 | probe.BuildMappers() 53 | probe.Collect() 54 | 55 | http.Start() 56 | ending() 57 | } 58 | 59 | // auto detect configuration file 60 | func aconf() { 61 | if *conf != "" && file.IsExist(*conf) { 62 | return 63 | } 64 | 65 | *conf = "etc/probe.local.yml" 66 | if file.IsExist(*conf) { 67 | return 68 | } 69 | 70 | *conf = "etc/probe.yml" 71 | if file.IsExist(*conf) { 72 | return 73 | } 74 | 75 | fmt.Println("no configuration file for probe") 76 | os.Exit(1) 77 | } 78 | 79 | // parse configuration file 80 | func pconf() { 81 | if err := config.Parse(*conf); err != nil { 82 | fmt.Println("cannot parse configuration file:", err) 83 | os.Exit(1) 84 | } 85 | } 86 | 87 | func ending() { 88 | c := make(chan os.Signal, 1) 89 | signal.Notify(c, syscall.SIGINT, syscall.SIGTERM, syscall.SIGQUIT) 90 | select { 91 | case <-c: 92 | fmt.Printf("stop signal caught, stopping... pid=%d\n", os.Getpid()) 93 | } 94 | 95 | logger.Close() 96 | http.Shutdown() 97 | fmt.Println("probe stopped successfully") 98 | } 99 | 100 | func start() { 101 | runner.Init() 102 | fmt.Println("probe start, use configuration file:", *conf) 103 | fmt.Println("runner.cwd:", runner.Cwd) 104 | } 105 | -------------------------------------------------------------------------------- /probe/core/clients.go: -------------------------------------------------------------------------------- 1 | package core 2 | 3 | import ( 4 | "net/rpc" 5 | "sync" 6 | ) 7 | 8 | type RpcClientContainer struct { 9 | M map[string]*rpc.Client 10 | sync.RWMutex 11 | } 12 | 13 | var rpcClients *RpcClientContainer 14 | 15 | func InitRpcClients() { 16 | rpcClients = &RpcClientContainer{ 17 | M: make(map[string]*rpc.Client), 18 | } 19 | } 20 | 21 | func (rcc *RpcClientContainer) Get(addr string) *rpc.Client { 22 | rcc.RLock() 23 | defer rcc.RUnlock() 24 | 25 | client, has := rcc.M[addr] 26 | if !has { 27 | return nil 28 | } 29 | 30 | return client 31 | } 32 | 33 | // Put 返回的bool表示affected,确实把自己塞进去了 34 | func (rcc *RpcClientContainer) Put(addr string, client *rpc.Client) bool { 35 | rcc.Lock() 36 | defer rcc.Unlock() 37 | 38 | oc, has := rcc.M[addr] 39 | if has && oc != nil { 40 | return false 41 | } 42 | 43 | rcc.M[addr] = client 44 | return true 45 | } 46 | 47 | func (rcc *RpcClientContainer) Del(addr string) { 48 | rcc.Lock() 49 | defer rcc.Unlock() 50 | delete(rcc.M, addr) 51 | } 52 | -------------------------------------------------------------------------------- /probe/core/common.go: -------------------------------------------------------------------------------- 1 | package core 2 | 3 | func GenProbeMetric(nid, metric string, val interface{}, tags map[string]string) *MetricValue { 4 | mv := MetricValue{ 5 | Nid: nid, 6 | Metric: metric, 7 | ValueUntyped: val, 8 | CounterType: GAUGE, 9 | TagsMap: map[string]string{}, 10 | } 11 | 12 | for k, v := range tags { 13 | mv.TagsMap[k] = v 14 | } 15 | 16 | return &mv 17 | } 18 | -------------------------------------------------------------------------------- /probe/core/dataobj.go: -------------------------------------------------------------------------------- 1 | package core 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "sort" 7 | "strconv" 8 | "strings" 9 | "sync" 10 | ) 11 | 12 | const ( 13 | GAUGE = "GAUGE" 14 | COUNTER = "COUNTER" 15 | SUBTRACT = "SUBTRACT" 16 | DERIVE = "DERIVE" 17 | SPLIT = "/" 18 | ) 19 | 20 | const ( 21 | MachineDep = 1 22 | MachineIndep = 2 23 | ) 24 | 25 | type MetricValue struct { 26 | Nid string `json:"nid"` 27 | Metric string `json:"metric"` 28 | Endpoint string `json:"endpoint"` 29 | Timestamp int64 `json:"timestamp"` 30 | Step int64 `json:"step"` 31 | ValueUntyped interface{} `json:"value"` 32 | Value float64 `json:"-"` 33 | CounterType string `json:"counterType"` 34 | Tags string `json:"tags"` 35 | TagsMap map[string]string `json:"tagsMap"` //保留2种格式,方便后端组件使用 36 | Extra string `json:"extra"` 37 | } 38 | 39 | var bufferPool = sync.Pool{ 40 | New: func() interface{} { 41 | return new(bytes.Buffer) 42 | }, 43 | } 44 | 45 | func (m *MetricValue) PK() string { 46 | ret := bufferPool.Get().(*bytes.Buffer) 47 | ret.Reset() 48 | defer bufferPool.Put(ret) 49 | 50 | if m.TagsMap == nil || len(m.TagsMap) == 0 { 51 | ret.WriteString(m.Endpoint) 52 | ret.WriteString(SPLIT) 53 | ret.WriteString(m.Metric) 54 | 55 | return ret.String() 56 | } 57 | ret.WriteString(m.Endpoint) 58 | ret.WriteString(SPLIT) 59 | ret.WriteString(m.Metric) 60 | ret.WriteString(SPLIT) 61 | ret.WriteString(SortedTags(m.TagsMap)) 62 | return ret.String() 63 | } 64 | 65 | func (m *MetricValue) CheckValidity(now int64) (err error) { 66 | if m == nil { 67 | err = fmt.Errorf("item is nil") 68 | return 69 | } 70 | 71 | if m.Nid == "" && m.Endpoint == "" { 72 | err = fmt.Errorf("nid or endpoint should not be empty") 73 | return 74 | } 75 | 76 | if m.Nid != "" { 77 | m.Endpoint = NidToEndpoint(m.Nid) 78 | } 79 | 80 | if m.Metric == "" { 81 | err = fmt.Errorf("metric should not be empty") 82 | return 83 | } 84 | 85 | // 检测保留字 86 | reservedWords := "[\\t] [\\r] [\\n] [,] [ ] [=]" 87 | if HasReservedWords(m.Metric) { 88 | err = fmt.Errorf("metric:%s contains reserved words: %s", m.Metric, reservedWords) 89 | return 90 | } 91 | if HasReservedWords(m.Endpoint) { 92 | err = fmt.Errorf("endpoint:%s contains reserved words: %s", m.Endpoint, reservedWords) 93 | return 94 | } 95 | 96 | if m.CounterType == "" { 97 | m.CounterType = GAUGE 98 | } 99 | 100 | if m.CounterType != GAUGE && m.CounterType != COUNTER && m.CounterType != SUBTRACT { 101 | err = fmt.Errorf("wrong counter type") 102 | return 103 | } 104 | 105 | if m.ValueUntyped == "" { 106 | err = fmt.Errorf("value is nil") 107 | return 108 | } 109 | 110 | if m.Step <= 0 { 111 | err = fmt.Errorf("step should larger than 0") 112 | return 113 | } 114 | 115 | if len(m.TagsMap) == 0 { 116 | m.TagsMap, err = SplitTagsString(m.Tags) 117 | if err != nil { 118 | return 119 | } 120 | } 121 | 122 | if len(m.TagsMap) > 20 { 123 | err = fmt.Errorf("tagkv count is too large > 20") 124 | } 125 | 126 | if len(m.Metric) > 128 { 127 | err = fmt.Errorf("len(m.Metric) is too large") 128 | return 129 | } 130 | 131 | for k, v := range m.TagsMap { 132 | delete(m.TagsMap, k) 133 | k = filterString(k) 134 | v = filterString(v) 135 | if len(k) == 0 || len(v) == 0 { 136 | err = fmt.Errorf("tag key and value should not be empty") 137 | return 138 | } 139 | 140 | m.TagsMap[k] = v 141 | } 142 | 143 | m.Tags = SortedTags(m.TagsMap) 144 | if len(m.Tags) > 512 { 145 | err = fmt.Errorf("len(m.Tags) is too large") 146 | return 147 | } 148 | 149 | //时间超前5分钟则报错 150 | if m.Timestamp-now > 300 { 151 | err = fmt.Errorf("point timestamp:%d is ahead of now:%d", m.Timestamp, now) 152 | return 153 | } 154 | 155 | if m.Timestamp <= 0 { 156 | m.Timestamp = now 157 | } 158 | 159 | m.Timestamp = alignTs(m.Timestamp, int64(m.Step)) 160 | 161 | valid := true 162 | var vv float64 163 | 164 | switch cv := m.ValueUntyped.(type) { 165 | case string: 166 | vv, err = strconv.ParseFloat(cv, 64) 167 | if err != nil { 168 | valid = false 169 | } 170 | case float64: 171 | vv = cv 172 | case uint64: 173 | vv = float64(cv) 174 | case int64: 175 | vv = float64(cv) 176 | case int: 177 | vv = float64(cv) 178 | default: 179 | valid = false 180 | } 181 | 182 | if !valid { 183 | err = fmt.Errorf("value [%v] is illegal", m.Value) 184 | return 185 | } 186 | 187 | m.Value = vv 188 | return 189 | } 190 | 191 | func HasReservedWords(str string) bool { 192 | idx := strings.IndexFunc(str, func(r rune) bool { 193 | return r == '\t' || 194 | r == '\r' || 195 | r == '\n' || 196 | r == ',' || 197 | r == ' ' || 198 | r == '=' 199 | }) 200 | return idx != -1 201 | } 202 | 203 | func filterString(str string) string { 204 | if -1 == strings.IndexFunc(str, 205 | func(r rune) bool { 206 | return r == '\t' || 207 | r == '\r' || 208 | r == '\n' || 209 | r == ',' || 210 | r == ' ' || 211 | r == '=' 212 | }) { 213 | 214 | return str 215 | } 216 | 217 | return strings.Map(func(r rune) rune { 218 | if r == '\t' || 219 | r == '\r' || 220 | r == '\n' || 221 | r == ',' || 222 | r == ' ' || 223 | r == '=' { 224 | return '_' 225 | } 226 | return r 227 | }, str) 228 | 229 | return str 230 | } 231 | 232 | func SortedTags(tags map[string]string) string { 233 | if tags == nil { 234 | return "" 235 | } 236 | 237 | size := len(tags) 238 | if size == 0 { 239 | return "" 240 | } 241 | 242 | ret := bufferPool.Get().(*bytes.Buffer) 243 | ret.Reset() 244 | defer bufferPool.Put(ret) 245 | 246 | if size == 1 { 247 | for k, v := range tags { 248 | ret.WriteString(k) 249 | ret.WriteString("=") 250 | ret.WriteString(v) 251 | } 252 | return ret.String() 253 | } 254 | 255 | keys := make([]string, size) 256 | i := 0 257 | for k := range tags { 258 | keys[i] = k 259 | i++ 260 | } 261 | sort.Strings(keys) 262 | 263 | for j, key := range keys { 264 | ret.WriteString(key) 265 | ret.WriteString("=") 266 | ret.WriteString(tags[key]) 267 | if j != size-1 { 268 | ret.WriteString(",") 269 | } 270 | } 271 | 272 | return ret.String() 273 | } 274 | 275 | func SplitTagsString(s string) (tags map[string]string, err error) { 276 | tags = make(map[string]string) 277 | 278 | s = strings.Replace(s, " ", "", -1) 279 | if s == "" { 280 | return 281 | } 282 | 283 | tagSlice := strings.Split(s, ",") 284 | for _, tag := range tagSlice { 285 | tagPair := strings.SplitN(tag, "=", 2) 286 | if len(tagPair) == 2 { 287 | tags[tagPair[0]] = tagPair[1] 288 | } else { 289 | err = fmt.Errorf("bad tag %s", tag) 290 | return 291 | } 292 | } 293 | 294 | return 295 | } 296 | 297 | func DictedTagstring(s string) map[string]string { 298 | if s == "" { 299 | return map[string]string{} 300 | } 301 | s = strings.Replace(s, " ", "", -1) 302 | 303 | result := make(map[string]string) 304 | tags := strings.Split(s, ",") 305 | for _, tag := range tags { 306 | pair := strings.SplitN(tag, "=", 2) 307 | if len(pair) == 2 { 308 | result[pair[0]] = pair[1] 309 | } 310 | } 311 | 312 | return result 313 | } 314 | 315 | func PKWithCounter(endpoint, counter string) string { 316 | ret := bufferPool.Get().(*bytes.Buffer) 317 | ret.Reset() 318 | defer bufferPool.Put(ret) 319 | 320 | ret.WriteString(endpoint) 321 | ret.WriteString("/") 322 | ret.WriteString(counter) 323 | 324 | return ret.String() 325 | } 326 | 327 | func GetCounter(metric, tag string, tagMap map[string]string) (counter string, err error) { 328 | if tagMap == nil { 329 | tagMap, err = SplitTagsString(tag) 330 | if err != nil { 331 | return 332 | } 333 | } 334 | 335 | tagStr := SortedTags(tagMap) 336 | counter = PKWithTags(metric, tagStr) 337 | return 338 | } 339 | 340 | func PKWithTags(metric, tags string) string { 341 | ret := bufferPool.Get().(*bytes.Buffer) 342 | ret.Reset() 343 | defer bufferPool.Put(ret) 344 | 345 | if tags == "" { 346 | ret.WriteString(metric) 347 | return ret.String() 348 | } 349 | ret.WriteString(metric) 350 | ret.WriteString("/") 351 | ret.WriteString(tags) 352 | return ret.String() 353 | } 354 | 355 | func PKWhitEndpointAndTags(endpoint, metric, tags string) string { 356 | ret := bufferPool.Get().(*bytes.Buffer) 357 | ret.Reset() 358 | defer bufferPool.Put(ret) 359 | 360 | if tags == "" { 361 | ret.WriteString(endpoint) 362 | ret.WriteString("/") 363 | ret.WriteString(metric) 364 | 365 | return ret.String() 366 | } 367 | ret.WriteString(endpoint) 368 | ret.WriteString("/") 369 | ret.WriteString(metric) 370 | ret.WriteString("/") 371 | ret.WriteString(tags) 372 | return ret.String() 373 | } 374 | 375 | // e.g. tcp.port.listen or proc.num 376 | type BuiltinMetric struct { 377 | Metric string 378 | Tags string 379 | } 380 | 381 | func (bm *BuiltinMetric) String() string { 382 | return fmt.Sprintf("%s/%s", bm.Metric, bm.Tags) 383 | } 384 | 385 | type BuiltinMetricRequest struct { 386 | Ty int 387 | IP string 388 | Checksum string 389 | } 390 | 391 | type BuiltinMetricResponse struct { 392 | Metrics []*BuiltinMetric 393 | Checksum string 394 | Timestamp int64 395 | ErrCode int 396 | } 397 | 398 | func (br *BuiltinMetricResponse) String() string { 399 | return fmt.Sprintf( 400 | "", 401 | br.Metrics, 402 | br.Checksum, 403 | br.Timestamp, 404 | ) 405 | } 406 | 407 | type BuiltinMetricSlice []*BuiltinMetric 408 | 409 | func (bm BuiltinMetricSlice) Len() int { 410 | return len(bm) 411 | } 412 | func (bm BuiltinMetricSlice) Swap(i, j int) { 413 | bm[i], bm[j] = bm[j], bm[i] 414 | } 415 | func (bm BuiltinMetricSlice) Less(i, j int) bool { 416 | return bm[i].String() < bm[j].String() 417 | } 418 | 419 | func alignTs(ts int64, period int64) int64 { 420 | return ts - ts%period 421 | } 422 | 423 | // code == 0 => success 424 | // code == 1 => bad request 425 | type SimpleRpcResponse struct { 426 | Code int `json:"code"` 427 | } 428 | 429 | type NullRpcRequest struct { 430 | } 431 | 432 | type TransferResp struct { 433 | Msg string 434 | Total int 435 | Invalid int 436 | Latency int64 437 | } 438 | 439 | func (t *TransferResp) String() string { 440 | s := fmt.Sprintf("TransferResp total=%d, err_invalid=%d, latency=%dms", 441 | t.Total, t.Invalid, t.Latency) 442 | if t.Msg != "" { 443 | s = fmt.Sprintf("%s, msg=%s", s, t.Msg) 444 | } 445 | return s 446 | } 447 | 448 | func NidToEndpoint(nid string) string { 449 | endpoint := "__nid__" + nid + "__" 450 | return endpoint 451 | } 452 | -------------------------------------------------------------------------------- /probe/core/push.go: -------------------------------------------------------------------------------- 1 | package core 2 | 3 | import ( 4 | "bufio" 5 | "fmt" 6 | "io" 7 | "math/rand" 8 | "net" 9 | "net/rpc" 10 | "reflect" 11 | "time" 12 | 13 | "github.com/toolkits/pkg/logger" 14 | "github.com/ugorji/go/codec" 15 | 16 | "github.com/shanghai-edu/n9e-probe/config" 17 | "github.com/shanghai-edu/n9e-probe/config/address" 18 | ) 19 | 20 | func Push(metricItems []*MetricValue) error { 21 | var err error 22 | var items []*MetricValue 23 | now := time.Now().Unix() 24 | 25 | for _, item := range metricItems { 26 | logger.Debugf("->recv:%+v", item) 27 | if err = item.CheckValidity(now); err != nil { 28 | msg := fmt.Errorf("metric:%v err:%v", item, err) 29 | logger.Warning(msg) 30 | // 如果数据有问题,直接跳过吧,比如mymon采集的到的数据,其实只有一个有问题,剩下的都没问题 31 | continue 32 | } 33 | logger.Debugf("push item: %+v", item) 34 | items = append(items, item) 35 | } 36 | 37 | addrs := address.GetRPCAddresses("transfer") 38 | count := len(addrs) 39 | retry := 0 40 | for { 41 | for _, i := range rand.Perm(count) { 42 | addr := addrs[i] 43 | reply, err := rpcCall(addr, items) 44 | if err != nil { 45 | logger.Error(err) 46 | continue 47 | } else { 48 | if reply.Msg != "ok" { 49 | err = fmt.Errorf("some item push err: %s", reply.Msg) 50 | logger.Error(err) 51 | } 52 | return err 53 | } 54 | } 55 | 56 | time.Sleep(time.Millisecond * 500) 57 | 58 | retry += 1 59 | if retry == 3 { 60 | break 61 | } 62 | } 63 | 64 | return err 65 | } 66 | 67 | func rpcCall(addr string, items []*MetricValue) (TransferResp, error) { 68 | var reply TransferResp 69 | var err error 70 | 71 | client := rpcClients.Get(addr) 72 | if client == nil { 73 | client, err = rpcClient(addr) 74 | if err != nil { 75 | return reply, err 76 | } 77 | affected := rpcClients.Put(addr, client) 78 | if !affected { 79 | defer func() { 80 | // 我尝试把自己这个client塞进map失败,说明已经有一个client塞进去了,那我自己用完了就关闭 81 | client.Close() 82 | }() 83 | 84 | } 85 | } 86 | 87 | timeout := time.Duration(8) * time.Second 88 | done := make(chan error, 1) 89 | 90 | go func() { 91 | err := client.Call(config.Config.Server.RpcMethod, items, &reply) 92 | done <- err 93 | }() 94 | 95 | select { 96 | case <-time.After(timeout): 97 | logger.Warningf("rpc call timeout, transfer addr: %s\n", addr) 98 | rpcClients.Put(addr, nil) 99 | client.Close() 100 | return reply, fmt.Errorf("%s rpc call timeout", addr) 101 | case err := <-done: 102 | if err != nil { 103 | rpcClients.Del(addr) 104 | client.Close() 105 | return reply, fmt.Errorf("%s rpc call done, but fail: %v", addr, err) 106 | } 107 | } 108 | 109 | return reply, nil 110 | } 111 | 112 | func rpcClient(addr string) (*rpc.Client, error) { 113 | conn, err := net.DialTimeout("tcp", addr, time.Second*3) 114 | if err != nil { 115 | err = fmt.Errorf("dial transfer %s fail: %v", addr, err) 116 | logger.Error(err) 117 | return nil, err 118 | } 119 | 120 | var bufConn = struct { 121 | io.Closer 122 | *bufio.Reader 123 | *bufio.Writer 124 | }{conn, bufio.NewReader(conn), bufio.NewWriter(conn)} 125 | 126 | var mh codec.MsgpackHandle 127 | mh.MapType = reflect.TypeOf(map[string]interface{}(nil)) 128 | 129 | rpcCodec := codec.MsgpackSpecRpc.ClientCodec(bufConn, &mh) 130 | client := rpc.NewClientWithCodec(rpcCodec) 131 | return client, nil 132 | } 133 | -------------------------------------------------------------------------------- /probe/cron.go: -------------------------------------------------------------------------------- 1 | package probe 2 | 3 | import ( 4 | "time" 5 | 6 | "github.com/shanghai-edu/n9e-probe/probe/core" 7 | ) 8 | 9 | func Collect() { 10 | 11 | for _, v := range Mappers { 12 | for _, f := range v.Fs { 13 | go collect(int64(v.Interval), f) 14 | } 15 | } 16 | } 17 | 18 | func collect(sec int64, fn func() []*core.MetricValue) { 19 | t := time.NewTicker(time.Second * time.Duration(sec)) 20 | defer t.Stop() 21 | 22 | for { 23 | <-t.C 24 | 25 | metricValues := []*core.MetricValue{} 26 | now := time.Now().Unix() 27 | 28 | items := fn() 29 | if items == nil || len(items) == 0 { 30 | continue 31 | } 32 | 33 | for _, item := range items { 34 | item.Step = sec 35 | item.Timestamp = now 36 | metricValues = append(metricValues, item) 37 | } 38 | core.Push(metricValues) 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /probe/func.go: -------------------------------------------------------------------------------- 1 | package probe 2 | 3 | import ( 4 | "github.com/shanghai-edu/n9e-probe/config" 5 | "github.com/shanghai-edu/n9e-probe/probe/core" 6 | ) 7 | 8 | type FuncsAndInterval struct { 9 | Fs []func() []*core.MetricValue 10 | Interval int 11 | } 12 | 13 | var Mappers []FuncsAndInterval 14 | 15 | func BuildMappers() { 16 | interval := config.Get().Probe.Interval 17 | Mappers = []FuncsAndInterval{ 18 | { 19 | Fs: []func() []*core.MetricValue{ 20 | PingMetrics, 21 | UrlMetrics, 22 | }, 23 | Interval: interval, 24 | }, 25 | } 26 | 27 | } 28 | -------------------------------------------------------------------------------- /probe/models/ping.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | import ( 4 | "fmt" 5 | 6 | "strconv" 7 | "time" 8 | 9 | "github.com/paulstuart/ping" 10 | ) 11 | 12 | type PingRes struct { 13 | Ip string 14 | Ping float64 15 | } 16 | 17 | func PingProbe(ips []string, limit, timeout int64) []PingRes { 18 | chLimit := make(chan bool, limit) //控制并发访问量 19 | chs := make([]chan PingRes, len(ips)) 20 | 21 | limitFunc := func(chLimit chan bool, ch chan PingRes, ip string) { 22 | pingProbe(ip, timeout, ch) 23 | <-chLimit 24 | } 25 | for i, ip := range ips { 26 | chs[i] = make(chan PingRes, 1) 27 | chLimit <- true 28 | go limitFunc(chLimit, chs[i], ip) 29 | } 30 | result := []PingRes{} 31 | for _, ch := range chs { 32 | res := <-ch 33 | result = append(result, res) 34 | } 35 | return result 36 | } 37 | 38 | func pingProbe(address string, timeout int64, ch chan PingRes) { 39 | var pingRes PingRes 40 | rtt := pingCheck(address, timeout) 41 | pingRes.Ip = address 42 | pingRes.Ping = rtt 43 | ch <- pingRes 44 | return 45 | } 46 | 47 | func pingCheck(address string, timeout int64) float64 { 48 | now := time.Now() 49 | 50 | if err := ping.Pinger(address, int(timeout)); err != nil { 51 | return -1.0 52 | } 53 | 54 | end := time.Now() 55 | d := end.Sub(now) 56 | 57 | rttStr := fmt.Sprintf("%.3f", float64(d.Nanoseconds())/1000000.0) 58 | rtt, _ := strconv.ParseFloat(rttStr, 64) 59 | 60 | return rtt 61 | } 62 | -------------------------------------------------------------------------------- /probe/models/probe.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | import ( 4 | "crypto/tls" 5 | "crypto/x509" 6 | "fmt" 7 | "math" 8 | "net/http" 9 | "net/url" 10 | "strconv" 11 | "strings" 12 | "time" 13 | 14 | "github.com/toolkits/pkg/logger" 15 | ) 16 | 17 | type UrlRes struct { 18 | Url *url.URL 19 | Cert int 20 | CertRemainingDay int 21 | Latency float64 22 | HttpStatusCode int 23 | } 24 | 25 | // UrlProbe 发起一个 url 拨测 26 | func UrlProbe(rawurls []string, headers map[string]string, limit, timeout int64) []UrlRes { 27 | chLimit := make(chan bool, limit) //控制并发访问量 28 | chs := make([]chan UrlRes, len(rawurls)) 29 | 30 | limitFunc := func(chLimit chan bool, ch chan UrlRes, rawurl string) { 31 | urlProbe(rawurl, headers, timeout, ch) 32 | <-chLimit 33 | } 34 | for i, rawurl := range rawurls { 35 | if err := urlValid(rawurl); err != nil { 36 | logger.Errorf("url is not valid, ignore: %s", rawurl) 37 | continue 38 | } 39 | chs[i] = make(chan UrlRes, 1) 40 | chLimit <- true 41 | go limitFunc(chLimit, chs[i], rawurl) 42 | } 43 | result := []UrlRes{} 44 | for _, ch := range chs { 45 | res := <-ch 46 | result = append(result, res) 47 | } 48 | return result 49 | } 50 | 51 | func urlProbe(rawurl string, headers map[string]string, timeout int64, ch chan UrlRes) { 52 | urlRes := urlCheck(rawurl, headers, timeout) 53 | ch <- urlRes 54 | return 55 | } 56 | 57 | func urlCheck(rawurl string, headers map[string]string, timeout int64) (res UrlRes) { 58 | var urlStruct *url.URL 59 | urlStruct, _ = url.Parse(rawurl) 60 | 61 | res.Url = urlStruct 62 | now := time.Now() 63 | 64 | var err error 65 | var statusCode int 66 | var certInfo *x509.Certificate 67 | statusCode, certInfo, err = httpGet(rawurl, headers, false, timeout) 68 | if err != nil && strings.Contains(err.Error(), "certificate") { 69 | now = time.Now() 70 | res.Cert = -1 71 | statusCode, _, err = httpGet(rawurl, headers, true, timeout) 72 | } 73 | end := time.Now() 74 | d := end.Sub(now) 75 | 76 | if err != nil { 77 | res.Latency = -1.0 78 | return 79 | } 80 | if urlStruct.Scheme == "https" { 81 | res.Cert = 1 82 | if certInfo != nil { 83 | logger.Debug(certInfo.NotAfter) 84 | d := certInfo.NotAfter.Sub(time.Now()) 85 | logger.Debug(time.Now()) 86 | logger.Debug(d, d.Hours()/24) 87 | res.CertRemainingDay, _ = strconv.Atoi(fmt.Sprintf("%.f", math.Floor(d.Hours()/24))) 88 | } 89 | } 90 | 91 | rttStr := fmt.Sprintf("%.3f", float64(d.Nanoseconds())/1000000.0) 92 | rtt, _ := strconv.ParseFloat(rttStr, 64) 93 | res.Latency = rtt 94 | res.HttpStatusCode = statusCode 95 | return 96 | } 97 | 98 | func httpGet(rawurl string, headers map[string]string, skipCert bool, timeout int64) (statusCode int, certInfo *x509.Certificate, err error) { 99 | req, err := http.NewRequest("GET", rawurl, nil) 100 | if err != nil { 101 | return 102 | } 103 | for k, v := range headers { 104 | req.Header.Set(k, v) 105 | } 106 | req.Close = true 107 | 108 | tr := &http.Transport{ 109 | TLSClientConfig: &tls.Config{InsecureSkipVerify: skipCert}, 110 | } 111 | 112 | client := &http.Client{ 113 | Transport: tr, 114 | Timeout: time.Second * time.Duration(timeout), 115 | } 116 | resp, err := client.Do(req) 117 | if err != nil { 118 | return 119 | } 120 | defer resp.Body.Close() 121 | if resp.TLS != nil { 122 | if len(resp.TLS.PeerCertificates) > 0 { 123 | certInfo = resp.TLS.PeerCertificates[0] 124 | } 125 | } 126 | statusCode = resp.StatusCode 127 | return 128 | } 129 | 130 | func urlValid(rawurl string) error { 131 | _, err := url.Parse(rawurl) 132 | return err 133 | } 134 | -------------------------------------------------------------------------------- /probe/models/probe_test.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | import ( 4 | "encoding/json" 5 | "testing" 6 | ) 7 | 8 | func Test_PingProbe(t *testing.T) { 9 | ips := []string{"114.114.114.114", "114.114.115.115", "39.156.69.79"} 10 | var timeout int64 = 2 11 | var limit int64 = 1 12 | res := PingProbe(ips, limit, timeout) 13 | bs, _ := json.Marshal(res) 14 | t.Log(string(bs)) 15 | } 16 | 17 | func Test_UrlProbe(t *testing.T) { 18 | urls := []string{"http://bbs.ngacn.cc/path/query?aaa=123&bbb=456", "https://www.16.com", "https://www.baidu.com"} 19 | headers := map[string]string{ 20 | "user-agent": "Mozilla/5.0 (Linux; Android 6.0.1; Moto G (4)) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Mobile Safari/537.36 Edg/87.0.664.66", 21 | } 22 | 23 | var timeout int64 = 2 24 | var limit int64 = 3 25 | res := UrlProbe(urls, headers, limit, timeout) 26 | bs, _ := json.Marshal(res) 27 | t.Log(string(bs)) 28 | } 29 | -------------------------------------------------------------------------------- /probe/ping.go: -------------------------------------------------------------------------------- 1 | package probe 2 | 3 | import ( 4 | "github.com/shanghai-edu/n9e-probe/config" 5 | "github.com/shanghai-edu/n9e-probe/probe/core" 6 | "github.com/shanghai-edu/n9e-probe/probe/models" 7 | "github.com/toolkits/pkg/logger" 8 | ) 9 | 10 | func PingMetrics() []*core.MetricValue { 11 | ret := []*core.MetricValue{} 12 | 13 | for nid, ips := range config.Get().Ping { 14 | results := models.PingProbe(ips, config.Get().Probe.Limit, config.Get().Probe.Timeout) 15 | for _, res := range results { 16 | logger.Debugf("ping result %+v", res) 17 | tags := map[string]string{ 18 | "ip": res.Ip, 19 | } 20 | if config.Get().Probe.Region != "" { 21 | tags["region"] = config.Get().Probe.Region 22 | } 23 | ret = append(ret, core.GenProbeMetric(nid, "ping.latency", res.Ping, tags)) 24 | 25 | } 26 | } 27 | return ret 28 | } 29 | -------------------------------------------------------------------------------- /probe/url.go: -------------------------------------------------------------------------------- 1 | package probe 2 | 3 | import ( 4 | "github.com/shanghai-edu/n9e-probe/config" 5 | "github.com/shanghai-edu/n9e-probe/probe/core" 6 | "github.com/shanghai-edu/n9e-probe/probe/models" 7 | "github.com/toolkits/pkg/logger" 8 | ) 9 | 10 | func UrlMetrics() []*core.MetricValue { 11 | ret := []*core.MetricValue{} 12 | 13 | for nid, urls := range config.Get().Url { 14 | results := models.UrlProbe(urls, config.Get().Probe.Headers, config.Get().Probe.Limit, config.Get().Probe.Timeout) 15 | for _, res := range results { 16 | logger.Debugf("url result %+v", res) 17 | tags := map[string]string{ 18 | "scheme": res.Url.Scheme, 19 | "host": res.Url.Host, 20 | } 21 | if res.Url.Path == "" { 22 | tags["path"] = "/" 23 | } else { 24 | tags["path"] = res.Url.Path 25 | } 26 | if config.Get().Probe.Region != "" { 27 | tags["region"] = config.Get().Probe.Region 28 | } 29 | ret = append(ret, core.GenProbeMetric(nid, "url.latency", res.Latency, tags)) 30 | ret = append(ret, core.GenProbeMetric(nid, "url.cert", res.Cert, tags)) 31 | ret = append(ret, core.GenProbeMetric(nid, "url.status_code", res.HttpStatusCode, tags)) 32 | if res.Cert == 1 { 33 | ret = append(ret, core.GenProbeMetric(nid, "url.cert_remaining_day", res.CertRemainingDay, tags)) 34 | } 35 | } 36 | } 37 | return ret 38 | } 39 | -------------------------------------------------------------------------------- /service/probe.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=probe 3 | After=network-online.target 4 | Wants=network-online.target 5 | 6 | [Service] 7 | # modify when deploy in prod env 8 | User=root 9 | Group=root 10 | 11 | Type=simple 12 | ExecStart=/home/n9e-probe/n9e-probe 13 | WorkingDirectory=/home/n9e-probe 14 | 15 | Restart=always 16 | RestartSec=1 17 | StartLimitInterval=0 18 | 19 | [Install] 20 | WantedBy=multi-user.target 21 | --------------------------------------------------------------------------------