diff --git a/conf.d/example.yml b/conf.d/example.yml new file mode 100644 index 0000000..e784e7f --- /dev/null +++ b/conf.d/example.yml @@ -0,0 +1,48 @@ +- url: "http://hq.sinajs.cn/list=sz002307" + request: + method: "GET" + trigger_by: + # 当该字符串不存在于返回结果中发送报警给alertmanager + response_contains: ["北新路桥"] + alert: + labels: + severity: critical + annotations: + description: "test api monitor" + + +- url: "https://xxxx.com" + request: + method: "POST" + data: '{xxx}' + add_header: + key: value + trigger_by: + # 此处传入一个正则表达式,没有匹配到正则表达式则发送报警给alertmanager + match_re: re.* + alert: + labels: + severity: critical + annotations: + description: "POST接口监控" + + +- url: "https://xxxx.com" + request: + method: "POST" + data: '{xxx}' + # 监控间隔,默认为全局配置中的Interval + interval: 30s + # 请求超时时间,默认为全局配置中的timeout, + timeout: 10s + add_header: + key: value + trigger_by: + # 如果返回值不等于预期的字符串则发送报警给alertmanager + expected: "response" + # 用法参考promethues rule里面的labels. + alert: + labels: + severity: critical + annotations: + description: "接口监控" \ No newline at end of file diff --git a/cruiser.yml b/cruiser.yml new file mode 100644 index 0000000..060d851 --- /dev/null +++ b/cruiser.yml @@ -0,0 +1,9 @@ +global: + alertmanager_api: "http://192.168.38.160:9093/api/v2/alerts" + interval: 1m + timeout: 10s + + +url_config: + include: + - "./conf.d/example.yml" diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..0691e0a --- /dev/null +++ b/go.mod @@ -0,0 +1,11 @@ +module github.com/chentiangang/cruiser + +go 1.14 + +require ( + github.com/chentiangang/xlog v0.0.0-20190715073137-dc027dedf2e0 + github.com/dgrijalva/jwt-go v3.2.0+incompatible + github.com/prometheus/client_golang v1.7.0 + github.com/prometheus/common v0.10.0 + gopkg.in/yaml.v2 v2.3.0 +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..2669399 --- /dev/null +++ b/go.sum @@ -0,0 +1,109 @@ +github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= +github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= +github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= +github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= +github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= +github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= +github.com/cespare/xxhash/v2 v2.1.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+qY= +github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/chentiangang/xlog v0.0.0-20190715073137-dc027dedf2e0 h1:G9c2dnlwnk84MYqGRK1vDNLh2EGPKxKYA7wwmqqoB1g= +github.com/chentiangang/xlog v0.0.0-20190715073137-dc027dedf2e0/go.mod h1:kfgigRpLqL2Q6DbQU0iqysfjmELPkV/lKvYwTOYTQIc= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dgrijalva/jwt-go v1.0.2 h1:KPldsxuKGsS2FPWsNeg9ZO18aCrGKujPoWXn2yo+KQM= +github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM= +github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= +github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= +github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= +github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= +github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.2 h1:+Z5KGCizgyZCbGh1KZqA0fcLLkwbsjIzS4aV2v7wJX0= +github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= +github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= +github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU= +github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= +github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= +github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= +github.com/prometheus/client_golang v1.7.0 h1:wCi7urQOGBsYcQROHqpUUX4ct84xp40t9R9JX0FuA/U= +github.com/prometheus/client_golang v1.7.0/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M= +github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= +github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.2.0 h1:uq5h0d+GuxiXLJLNABMgp2qUWDPiLvgCzz2dUR+/W/M= +github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= +github.com/prometheus/common v0.10.0 h1:RyRA7RzGXQZiW+tGMr7sxa85G1z0yOpM1qq5c8lNawc= +github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo= +github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= +github.com/prometheus/procfs v0.1.3 h1:F0+tqvhOksq22sc6iCHF5WGlWjdwj92p0udFh1VFBS8= +github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= +github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= +github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1 h1:ogLJMz+qpzav7lGMh10LMvAkM/fAoGlaiiHYiFYdm80= +golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.23.0 h1:4MY060fB1DLGMB/7MBTLnwQUY6+F09GEiz6SsrNqyzM= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU= +gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= diff --git a/main.go b/main.go new file mode 100644 index 0000000..bff9e6c --- /dev/null +++ b/main.go @@ -0,0 +1,121 @@ +package main + +import ( + "bytes" + "encoding/json" + "flag" + "fmt" + "io/ioutil" + "net/http" + "reflect" + "time" + + "github.com/chentiangang/cruiser/modules" + "github.com/chentiangang/cruiser/modules/url" + + "github.com/chentiangang/xlog" + + "github.com/prometheus/client_golang/prometheus/promhttp" + yaml "gopkg.in/yaml.v2" +) + +var cfg modules.Config + +//const collectTime = time.Second * 10 +const filename = "./cruiser.yml" + +func NewCruiser(name string) modules.Cruiser { + if name == "url" { + return url.UrlConfig{} + } + + panic(fmt.Sprintf("Not found %s\n", name)) + return nil +} + +func main() { + var addr = flag.String("listen-address", ":8080", "The address to listen on for HTTP requests.") + flag.Parse() + + cfg.Task = make(chan modules.Cruiser, 50) + + bs, err := ioutil.ReadFile(filename) + if err != nil { + panic(err) + } + err = yaml.Unmarshal(bs, &cfg) + if err != nil { + panic(err) + } + + xlog.LogDebug("%+v", cfg) + go Run() + + if !reflect.DeepEqual(cfg.UrlConfig, modules.UrlConfig{}) { + cruiser := NewCruiser("url") + err = cruiser.LoadConfig(&cfg) + if err != nil { + panic(err) + } + } + + close(cfg.Task) + + http.Handle("/metrics", promhttp.Handler()) + xlog.LogFatal("%s", http.ListenAndServe(*addr, nil)) +} + +func SendAlert(alert modules.Alert) { + var Alerts []modules.Alert + Alerts = append(Alerts, alert) + + bs, err := json.Marshal(Alerts) + if err != nil { + xlog.LogError("%s", err) + panic(err) + } + + //xlog.LogInfo("%s", bs) + client := &http.Client{} + req, err := http.NewRequest("POST", cfg.Global.AlertmanagerApi, bytes.NewReader(bs)) + + req.Header.Add("Content-Type", "application/json") + if err != nil { + panic(err) + } + + _, err = client.Do(req) + if err != nil { + xlog.LogError("alert data: %s, error: %s", bs, err) + panic(err) + } + xlog.LogWarn("send alert: %s", bs) +} + +func Run() { + for task := range cfg.Task { + xlog.LogDebug("%+v", task) + go func(i modules.Cruiser) { + ticker := time.NewTicker(i.GetInterval()) + defer ticker.Stop() + + // 启动完成后即发送一个请求,避免调试等待时间 + alert := i.SendRequest() + if !reflect.DeepEqual(alert, modules.Alert{}) { + SendAlert(alert) + } + + // 定时器 + for { + select { + case <-ticker.C: + alert = i.SendRequest() + //xlog.LogDebug("%+v", alert) + if !reflect.DeepEqual(alert, modules.Alert{}) { + go SendAlert(alert) + } + } + } + }(task) + } +} diff --git a/modules/mode.go b/modules/mode.go new file mode 100644 index 0000000..e0b56bb --- /dev/null +++ b/modules/mode.go @@ -0,0 +1,43 @@ +package modules + +import ( + "time" + + "github.com/prometheus/common/model" +) + +type Config struct { + Global Global `yaml:"global"` + UrlConfig UrlConfig `yaml:"url_config"` + Task chan Cruiser +} + +type UrlConfig struct { + Include []string `yaml:"include"` +} + +type Global struct { + AlertmanagerApi string `yaml:"alertmanager_api"` + Interval model.Duration `yaml:"interval,omitempty"` + Timeout model.Duration `yaml:"timeout,omitempty"` +} + +type Alert struct { + Labels map[string]string `yaml:"labels,omitempty" json:"labels"` + Annotations map[string]string `yaml:"annotations,omitempty" json:"annotations"` + GeneratorURL string `yaml:"generatorURL" json:"generatorURL"` + //StartsAt time.Time `json:"startsAt"` + //EndsAt time.Time `json:"endsAt"` + //isEnd chan bool +} + +type Cruiser interface { + // 加载配置文件 + LoadConfig(cfg *Config) error + + // 获取请求间隔 + GetInterval() time.Duration + + // 发送请求,返回报警信息 + SendRequest() Alert +} diff --git a/modules/url/loadconfig.go b/modules/url/loadconfig.go new file mode 100644 index 0000000..30ccd56 --- /dev/null +++ b/modules/url/loadconfig.go @@ -0,0 +1,74 @@ +package url + +import ( + "io/ioutil" + "time" + + "github.com/chentiangang/cruiser/modules" + + yaml "gopkg.in/yaml.v2" +) + +//func loadConfig() { +// var cfg +//} + +func (u UrlConfig) LoadConfig(cfg *modules.Config) (err error) { + for _, i := range cfg.UrlConfig.Include { + bs, err := ioutil.ReadFile(i) + if err != nil { + panic(err) + } + + var urls []UrlConfig + err = yaml.Unmarshal(bs, &urls) + if err != nil { + panic(err) + } + + for _, i := range urls { + i.setInterval(cfg.Global.Interval.String()) + i.setTimeout(cfg.Global.Timeout.String()) + i.setDefaultAlert() + //xlog.LogDebug("%+v", i.Url) + cfg.Task <- i + } + } + + return nil +} + +func (u *UrlConfig) setInterval(interval string) { + if u.Request.Interval.String() == "0s" { + if interval == "" { + u.Request.Interval.Set("1m") + } + u.Request.Interval.Set(interval) + } +} + +func (u *UrlConfig) setTimeout(timeout string) { + if u.Request.Timeout.String() == "0s" { + if timeout == "0s" { + u.Request.Timeout.Set("10s") + } + u.Request.Timeout.Set(timeout) + } +} + +func (u *UrlConfig) setDefaultAlert() { + u.Alert.Labels["url"] = u.Url + u.Alert.GeneratorURL = "http://localhost:8080/metrics" + + if len(u.Alert.Annotations) == 0 { + u.Alert.Annotations = make(map[string]string, 2) + } +} + +func (u UrlConfig) GetInterval() time.Duration { + duration, err := time.ParseDuration(u.Request.Interval.String()) + if err != nil { + panic(err) + } + return duration +} diff --git a/modules/url/request.go b/modules/url/request.go new file mode 100644 index 0000000..0e485b0 --- /dev/null +++ b/modules/url/request.go @@ -0,0 +1,63 @@ +package url + +import ( + "bytes" + "fmt" + "net/http" + "time" + + "github.com/chentiangang/cruiser/modules" + + "github.com/chentiangang/xlog" + "github.com/prometheus/common/model" +) + +type UrlConfig struct { + Url string `yaml:"url"` + Request struct { + Method string `yaml:"method"` + Data string `yaml:"data"` + Header map[string]string `yaml:"add_header,omitempty"` + Interval model.Duration `yaml:"interval,omitempty"` + Timeout model.Duration `yaml:"timeout,omitempty"` + } `yaml:"request"` + // 这个结构包含所有触发条件的实现 + TriggerBy TriggerBy `yaml:"trigger_by"` + + Alert modules.Alert `yaml:"alert"` +} + +func (u UrlConfig) newHttpClient() *http.Client { + t, err := time.ParseDuration(u.Request.Timeout.String()) + if err != nil { + panic(err) + } + return &http.Client{ + Timeout: t, + } +} + +func (u UrlConfig) addHeader(req *http.Request) { + for key, value := range u.Request.Header { + req.Header.Add(key, value) + } +} + +func (u UrlConfig) do() (resp *http.Response, err error) { + client := u.newHttpClient() + + req, _ := http.NewRequest(u.Request.Method, u.Url, bytes.NewReader([]byte(u.Request.Data))) + u.addHeader(req) + return client.Do(req) +} + +func (u UrlConfig) SendRequest() modules.Alert { + resp, err := u.do() + xlog.LogDebug("requst url: %s", u.Url) + if err != nil { + u.Alert.Annotations["summary"] = fmt.Sprintf("URL: %s,\nERROR: \"%s\"", u.Url, err) + return u.Alert + } + + return u.trigger(resp) +} diff --git a/modules/url/trigger.go b/modules/url/trigger.go new file mode 100644 index 0000000..bddfd27 --- /dev/null +++ b/modules/url/trigger.go @@ -0,0 +1,112 @@ +package url + +import ( + "fmt" + "io/ioutil" + "net/http" + "regexp" + "strings" + + "github.com/chentiangang/cruiser/modules" +) + +type TriggerBy struct { + // 当返回的值包含改字段的字符串 代表接口正常,否则报警 + ResponseContains []string `yaml:"response_contains,omitempty"` + + // 当返回值和改正则表达式能匹配到结果 代表接口正常,否则报警 + MatchRe string `yaml:"match_re,omitempty"` + + // 当返回值等于expected 代表接口正常,否则报警 + Expected string `yaml:"expected,omitempty"` + + // 当该表达式的结果为true 代表接口正常,否则报警 + Expr string `yaml:"expr,omitempty"` +} + +func (u UrlConfig) trigger(resp *http.Response) modules.Alert { + + var alertMessage string + + switch { + case u.TriggerBy.byResponseContains(): + alertMessage = u.triggerByResponseContains(resp) + case u.TriggerBy.byMatchRe(): + alertMessage = u.triggerByMatchRe(resp) + case u.TriggerBy.byExpected(): + alertMessage = u.triggerByExpected(resp) + default: + alertMessage = fmt.Sprintf("%s\nERROR: \"%s\"", u.Url, "Not found the trigger conditions. Check YAML config") + } + + if alertMessage != "" { + u.Alert.Annotations["summary"] = alertMessage + return u.Alert + } + return modules.Alert{} +} + +func (u UrlConfig) triggerByResponseContains(resp *http.Response) (msg string) { + + body := respBodyString(resp) + var str []string + for _, i := range u.TriggerBy.ResponseContains { + if !strings.Contains(body, i) { + str = append(str, i) + } + } + + if str != nil { + u.Alert.Labels["ResponseContains"] = fmt.Sprintf("%s", str) + return fmt.Sprintf("%s\n返回值:\n%s\n不包含:\n%s", u.Url, body, str) + } + return +} + +func (u UrlConfig) triggerByMatchRe(resp *http.Response) (msg string) { + body := respBodyString(resp) + + re := regexp.MustCompile(u.TriggerBy.MatchRe) + //if len(body) < 200 { + // xlog.LogDebug("url: %s, current value: %s", u.Url, body) + //} + //xlog.LogDebug("url: %s, match value: %s", u.Url, re.FindString(body)) + if re.FindString(body) == "" { + msg = fmt.Sprintf("%s\n"+ + "测试用例:\n %s\n"+ + "当前值:\n %s", + u.Url, u.Request.Data, body) + return msg + } + return +} + +func (u UrlConfig) triggerByExpected(resp *http.Response) (msg string) { + body := respBodyString(resp) + if body != u.TriggerBy.Expected { + return fmt.Sprintf("%s\n当前值:\n%s\n,预期值:\n%s\n,statusCode:\n%d", u.Url, body, u.TriggerBy.Expected, resp.Status) + } + return +} + +func (u UrlConfig) triggerByExpr(resp *http.Response) (msg string) { + return +} + +func (t TriggerBy) byResponseContains() bool { + return t.ResponseContains != nil +} + +func (t TriggerBy) byMatchRe() bool { + return t.MatchRe != "" +} + +func (t TriggerBy) byExpected() bool { + return t.Expected != "" +} + +func respBodyString(resp *http.Response) string { + body, _ := ioutil.ReadAll(resp.Body) + defer resp.Body.Close() + return string(body) +}