diff --git a/Gopkg.lock b/Gopkg.lock index 21e386ae2b0..7ddaa066a6d 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -543,6 +543,12 @@ packages = ["."] revision = "5015f896ab62d3e9fe757456c757521ce0c3faff" +[[projects]] + name = "github.com/weaveworks/promrus" + packages = ["."] + revision = "0eadec5d0f597c43c2107b8fee02b7ea7f92e9b2" + version = "v1.0.0-legacy" + [[projects]] branch = "master" name = "golang.org/x/crypto" @@ -636,6 +642,6 @@ [solve-meta] analyzer-name = "dep" analyzer-version = 1 - inputs-digest = "66364b9ca354031a147a4cc7f92dcb00ff3a323a95ae1a6459714fddc65d7f39" + inputs-digest = "2d013ed5c812b3170cfb22111cbfd6d180bd43ca85496f0ccea0a31d38082cf2" solver-name = "gps-cdcl" solver-version = 1 diff --git a/Gopkg.toml b/Gopkg.toml index 2cfed3b1212..66093a14750 100644 --- a/Gopkg.toml +++ b/Gopkg.toml @@ -10,6 +10,10 @@ name = "github.com/weaveworks/common" branch = "master" +[[constraint]] + name = "github.com/weaveworks/promrus" + version = "v1.0.0-legacy" + [[override]] name = "github.com/Azure/azure-sdk-for-go" revision = "bd73d950fa4440dae889bd9917bff7cef539f86e" diff --git a/cmd/alertmanager/main.go b/cmd/alertmanager/main.go index 9edd12f22fc..71667911b7b 100644 --- a/cmd/alertmanager/main.go +++ b/cmd/alertmanager/main.go @@ -15,14 +15,15 @@ package main import ( "flag" - "log" "google.golang.org/grpc" + "github.com/prometheus/common/log" "github.com/weaveworks/common/middleware" "github.com/weaveworks/common/server" "github.com/weaveworks/cortex/pkg/alertmanager" "github.com/weaveworks/cortex/pkg/util" + "github.com/weaveworks/promrus" ) func main() { @@ -37,6 +38,11 @@ func main() { ) util.RegisterFlags(&serverConfig, &alertmanagerConfig) flag.Parse() + hook, err := promrus.NewPrometheusHook() + if err != nil { + log.Fatalf("Error initializing promrus: %v", err) + } + log.AddHook(hook) multiAM, err := alertmanager.NewMultitenantAlertmanager(&alertmanagerConfig) if err != nil { diff --git a/cmd/configs/main.go b/cmd/configs/main.go index e95b08262ea..32f93e4e30a 100644 --- a/cmd/configs/main.go +++ b/cmd/configs/main.go @@ -11,6 +11,7 @@ import ( "github.com/weaveworks/cortex/pkg/configs/api" "github.com/weaveworks/cortex/pkg/configs/db" "github.com/weaveworks/cortex/pkg/util" + "github.com/weaveworks/promrus" ) func main() { @@ -28,6 +29,12 @@ func main() { util.RegisterFlags(&serverConfig, &dbConfig) flag.Parse() + hook, err := promrus.NewPrometheusHook() + if err != nil { + log.Fatalf("Error initializing promrus: %v", err) + } + log.AddHook(hook) + db, err := db.New(dbConfig) if err != nil { log.Fatalf("Error initializing database: %v", err) diff --git a/cmd/distributor/main.go b/cmd/distributor/main.go index 771795864b3..d67d5b44855 100644 --- a/cmd/distributor/main.go +++ b/cmd/distributor/main.go @@ -13,6 +13,7 @@ import ( "github.com/weaveworks/cortex/pkg/distributor" "github.com/weaveworks/cortex/pkg/ring" "github.com/weaveworks/cortex/pkg/util" + "github.com/weaveworks/promrus" ) func main() { @@ -45,6 +46,11 @@ func main() { ) util.RegisterFlags(&serverConfig, &ringConfig, &distributorConfig) flag.Parse() + hook, err := promrus.NewPrometheusHook() + if err != nil { + log.Fatalf("Error initializing promrus: %v", err) + } + log.AddHook(hook) r, err := ring.New(ringConfig) if err != nil { diff --git a/cmd/ingester/main.go b/cmd/ingester/main.go index fb162a3fe2d..f210cb737f2 100644 --- a/cmd/ingester/main.go +++ b/cmd/ingester/main.go @@ -15,6 +15,7 @@ import ( "github.com/weaveworks/cortex/pkg/ingester" "github.com/weaveworks/cortex/pkg/ingester/client" "github.com/weaveworks/cortex/pkg/util" + "github.com/weaveworks/promrus" ) func main() { @@ -36,6 +37,12 @@ func main() { &schemaConfig, &ingesterConfig) flag.Parse() + hook, err := promrus.NewPrometheusHook() + if err != nil { + log.Fatalf("Error initializing promrus: %v", err) + } + log.AddHook(hook) + server, err := server.New(serverConfig) if err != nil { log.Fatalf("Error initializing server: %v", err) diff --git a/cmd/lite/main.go b/cmd/lite/main.go index 0e4b98143c0..0347536c422 100644 --- a/cmd/lite/main.go +++ b/cmd/lite/main.go @@ -24,6 +24,7 @@ import ( "github.com/weaveworks/cortex/pkg/ring" "github.com/weaveworks/cortex/pkg/ruler" "github.com/weaveworks/cortex/pkg/util" + "github.com/weaveworks/promrus" ) func main() { @@ -51,6 +52,12 @@ func main() { flag.BoolVar(&unauthenticated, "unauthenticated", false, "Set to true to disable multitenancy.") flag.Parse() + hook, err := promrus.NewPrometheusHook() + if err != nil { + log.Fatalf("Error initializing promrus: %v", err) + } + log.AddHook(hook) + server, err := server.New(serverConfig) if err != nil { log.Fatalf("Error initializing server: %v", err) diff --git a/cmd/querier/main.go b/cmd/querier/main.go index 75046de5a8f..3a9a38f443b 100644 --- a/cmd/querier/main.go +++ b/cmd/querier/main.go @@ -21,6 +21,7 @@ import ( "github.com/weaveworks/cortex/pkg/querier" "github.com/weaveworks/cortex/pkg/ring" "github.com/weaveworks/cortex/pkg/util" + "github.com/weaveworks/promrus" ) func main() { @@ -41,6 +42,12 @@ func main() { &chunkStoreConfig, &schemaConfig, &storageConfig) flag.Parse() + hook, err := promrus.NewPrometheusHook() + if err != nil { + log.Fatalf("Error initializing promrus: %v", err) + } + log.AddHook(hook) + r, err := ring.New(ringConfig) if err != nil { log.Fatalf("Error initializing ring: %v", err) diff --git a/cmd/ruler/main.go b/cmd/ruler/main.go index df0f4f8c2cc..2a808d86c6e 100644 --- a/cmd/ruler/main.go +++ b/cmd/ruler/main.go @@ -15,6 +15,7 @@ import ( "github.com/weaveworks/cortex/pkg/ring" "github.com/weaveworks/cortex/pkg/ruler" "github.com/weaveworks/cortex/pkg/util" + "github.com/weaveworks/promrus" ) func main() { @@ -36,6 +37,12 @@ func main() { &rulerConfig, &chunkStoreConfig, &storageConfig, &schemaConfig) flag.Parse() + hook, err := promrus.NewPrometheusHook() + if err != nil { + log.Fatalf("Error initializing promrus: %v", err) + } + log.AddHook(hook) + storageClient, err := storage.NewStorageClient(storageConfig, schemaConfig) if err != nil { log.Fatalf("Error initializing storage client: %v", err) diff --git a/cmd/table-manager/main.go b/cmd/table-manager/main.go index 8052443c0f8..bdfc74489d2 100644 --- a/cmd/table-manager/main.go +++ b/cmd/table-manager/main.go @@ -11,6 +11,7 @@ import ( "github.com/weaveworks/cortex/pkg/chunk" "github.com/weaveworks/cortex/pkg/chunk/storage" "github.com/weaveworks/cortex/pkg/util" + "github.com/weaveworks/promrus" ) func main() { @@ -27,6 +28,11 @@ func main() { ) util.RegisterFlags(&serverConfig, &storageConfig, &schemaConfig) flag.Parse() + hook, err := promrus.NewPrometheusHook() + if err != nil { + log.Fatalf("Error initializing promrus: %v", err) + } + log.AddHook(hook) if (schemaConfig.ChunkTables.WriteScale.Enabled || schemaConfig.IndexTables.WriteScale.Enabled || diff --git a/vendor/github.com/mwitkow/go-grpc-middleware/README.MD b/vendor/github.com/mwitkow/go-grpc-middleware/README.md similarity index 100% rename from vendor/github.com/mwitkow/go-grpc-middleware/README.MD rename to vendor/github.com/mwitkow/go-grpc-middleware/README.md diff --git a/vendor/github.com/prometheus/common/log/log.go b/vendor/github.com/prometheus/common/log/log.go index efad4842f31..6398959ac6b 100644 --- a/vendor/github.com/prometheus/common/log/log.go +++ b/vendor/github.com/prometheus/common/log/log.go @@ -351,6 +351,11 @@ func Fatalf(format string, args ...interface{}) { baseLogger.sourced().Fatalf(format, args...) } +// AddHook adds hook to Prometheus' original logger. +func AddHook(hook logrus.Hook) { + origLogger.Hooks.Add(hook) +} + type errorLogWriter struct{} func (errorLogWriter) Write(b []byte) (int, error) { diff --git a/vendor/github.com/weaveworks/promrus/.gitignore b/vendor/github.com/weaveworks/promrus/.gitignore new file mode 100644 index 00000000000..a1338d68517 --- /dev/null +++ b/vendor/github.com/weaveworks/promrus/.gitignore @@ -0,0 +1,14 @@ +# Binaries for programs and plugins +*.exe +*.dll +*.so +*.dylib + +# Test binary, build with `go test -c` +*.test + +# Output of the go coverage tool, specifically when used with LiteIDE +*.out + +# Project-local glide cache, RE: https://github.com/Masterminds/glide/issues/736 +.glide/ diff --git a/vendor/github.com/weaveworks/promrus/.travis.yml b/vendor/github.com/weaveworks/promrus/.travis.yml new file mode 100644 index 00000000000..7d6b8c0dec1 --- /dev/null +++ b/vendor/github.com/weaveworks/promrus/.travis.yml @@ -0,0 +1,15 @@ +language: go +sudo: false +go: + - 1.x + - tip +install: + - go get github.com/golang/dep/cmd/dep + - go get golang.org/x/tools/cmd/cover + - go get github.com/mattn/goveralls + - dep ensure + - go build +script: + - go vet + - go test -v -covermode=count -coverprofile=coverage.out + - $HOME/gopath/bin/goveralls -coverprofile=coverage.out -service=travis-ci diff --git a/vendor/github.com/weaveworks/promrus/Gopkg.lock b/vendor/github.com/weaveworks/promrus/Gopkg.lock new file mode 100644 index 00000000000..1e594157b3d --- /dev/null +++ b/vendor/github.com/weaveworks/promrus/Gopkg.lock @@ -0,0 +1,87 @@ +# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'. + + +[[projects]] + branch = "master" + name = "github.com/beorn7/perks" + packages = ["quantile"] + revision = "4c0e84591b9aa9e6dcfdf3e020114cd81f89d5f9" + +[[projects]] + name = "github.com/davecgh/go-spew" + packages = ["spew"] + revision = "346938d642f2ec3594ed81d874461961cd0faa76" + version = "v1.1.0" + +[[projects]] + branch = "master" + name = "github.com/golang/protobuf" + packages = ["proto"] + revision = "130e6b02ab059e7b717a096f397c5b60111cae74" + +[[projects]] + name = "github.com/matttproud/golang_protobuf_extensions" + packages = ["pbutil"] + revision = "3247c84500bff8d9fb6d579d800f20b3e091582c" + version = "v1.0.0" + +[[projects]] + name = "github.com/pmezard/go-difflib" + packages = ["difflib"] + revision = "792786c7400a136282c1664665ae0a8db921c6c2" + version = "v1.0.0" + +[[projects]] + name = "github.com/prometheus/client_golang" + packages = ["prometheus"] + revision = "c5b7fccd204277076155f10851dad72b76a49317" + version = "v0.8.0" + +[[projects]] + branch = "master" + name = "github.com/prometheus/client_model" + packages = ["go"] + revision = "6f3806018612930941127f2a7c6c453ba2c527d2" + +[[projects]] + branch = "master" + name = "github.com/prometheus/common" + packages = ["expfmt","internal/bitbucket.org/ww/goautoneg","model"] + revision = "2f17f4a9d485bf34b4bfaccc273805040e4f86c8" + +[[projects]] + branch = "master" + name = "github.com/prometheus/procfs" + packages = [".","xfs"] + revision = "e645f4e5aaa8506fc71d6edbc5c4ff02c04c46f2" + +[[projects]] + name = "github.com/Sirupsen/logrus" + packages = ["."] + revision = "f006c2ac4710855cf0f916dd6b77acf6b048dc6e" + version = "v1.0.3" + +[[projects]] + name = "github.com/stretchr/testify" + packages = ["assert"] + revision = "69483b4bd14f5845b5a1e55bca19e954e827f1d0" + version = "v1.1.4" + +[[projects]] + branch = "master" + name = "golang.org/x/crypto" + packages = ["ssh/terminal"] + revision = "9419663f5a44be8b34ca85f08abc5fe1be11f8a3" + +[[projects]] + branch = "master" + name = "golang.org/x/sys" + packages = ["unix","windows"] + revision = "314a259e304ff91bd6985da2a7149bbf91237993" + +[solve-meta] + analyzer-name = "dep" + analyzer-version = 1 + inputs-digest = "36d94413e5fdab027b84850df466089880bec5abaff8f69c5ad07c1532758f3d" + solver-name = "gps-cdcl" + solver-version = 1 diff --git a/vendor/github.com/weaveworks/promrus/Gopkg.toml b/vendor/github.com/weaveworks/promrus/Gopkg.toml new file mode 100644 index 00000000000..9425a542966 --- /dev/null +++ b/vendor/github.com/weaveworks/promrus/Gopkg.toml @@ -0,0 +1,22 @@ + +# Gopkg.toml example +# +# Refer to https://github.com/golang/dep/blob/master/docs/Gopkg.toml.md +# for detailed Gopkg.toml documentation. +# +# required = ["github.com/user/thing/cmd/thing"] +# ignored = ["github.com/user/project/pkgX", "bitbucket.org/user/project/pkgA/pkgY"] +# +# [[constraint]] +# name = "github.com/user/project" +# version = "1.0.0" +# +# [[constraint]] +# name = "github.com/user/project2" +# branch = "dev" +# source = "github.com/myfork/project2" +# +# [[override]] +# name = "github.com/x/y" +# version = "2.4.0" + diff --git a/vendor/github.com/weaveworks/promrus/LICENSE b/vendor/github.com/weaveworks/promrus/LICENSE new file mode 100644 index 00000000000..8dada3edaf5 --- /dev/null +++ b/vendor/github.com/weaveworks/promrus/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright {yyyy} {name of copyright owner} + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/vendor/github.com/weaveworks/promrus/README.md b/vendor/github.com/weaveworks/promrus/README.md new file mode 100644 index 00000000000..3e164045412 --- /dev/null +++ b/vendor/github.com/weaveworks/promrus/README.md @@ -0,0 +1,108 @@ +
+ +# promrus +Logrus hook to expose the number of log messages as Prometheus metrics: +``` +log_messages{level="debug"} +log_messages{level="info"} +log_messages{level="warning"} +log_messages{level="error"} +``` + +## Usage + +Sample code: +``` +package main + +import ( + "net/http" + "time" + + "github.com/prometheus/client_golang/prometheus/promhttp" + log "github.com/sirupsen/logrus" + "github.com/weaveworks/promrus" +) + +func main() { + // Create the Prometheus hook: + hook, err := promrus.NewPrometheusHook() + if err != nil { + return + } + + // Configure logrus to use the Prometheus hook: + log.AddHook(hook) + + // Expose Prometheus metrics via HTTP, as you usually would: + go http.ListenAndServe(":8080", promhttp.Handler()) + + // Log with logrus, as you usually would. + // Every time the program generates a log message, a Prometheus counter is incremented for the corresponding level. + for { + log.Infof("foo") + time.Sleep(1 * time.Second) + } +} +``` + +Run the above program: +``` +$ go get -u github.com/golang/dep/cmd/dep +$ dep ensure +$ go run main.go +INFO[0000] foo +INFO[0001] foo +INFO[0002] foo +[...] +INFO[0042] foo +``` + +Scrape the Prometheus metrics exposed by the hook: +``` +$ curl -fsS localhost:8080 | grep log_messages +# HELP log_messages Total number of log messages. +# TYPE log_messages counter +log_messages{level="debug"} 0 +log_messages{level="error"} 0 +log_messages{level="info"} 42 +log_messages{level="warning"} 0 +``` + +## Setup development environment +``` +$ go get github.com/golang/dep/cmd/dep +$ dep ensure +``` + +## Compile +``` +$ go build +``` + +## Test +``` +$ go test +DEBU[0000] this is at debug level! +INFO[0000] this is at info level! +WARN[0000] this is at warning level! +ERRO[0000] this is at error level! +PASS +ok github.com/weaveworks/promrus 0.011s +``` diff --git a/vendor/github.com/weaveworks/promrus/promrus.go b/vendor/github.com/weaveworks/promrus/promrus.go new file mode 100644 index 00000000000..af9ea24334a --- /dev/null +++ b/vendor/github.com/weaveworks/promrus/promrus.go @@ -0,0 +1,45 @@ +package promrus + +import ( + "github.com/Sirupsen/logrus" + "github.com/prometheus/client_golang/prometheus" +) + +// PrometheusHook exposes Prometheus counters for each of logrus' log levels. +type PrometheusHook struct { + counterVec *prometheus.CounterVec +} + +var supportedLevels = []logrus.Level{logrus.DebugLevel, logrus.InfoLevel, logrus.WarnLevel, logrus.ErrorLevel} + +// NewPrometheusHook creates a new instance of PrometheusHook which exposes Prometheus counters for various log levels. +func NewPrometheusHook() (*PrometheusHook, error) { + counterVec := prometheus.NewCounterVec(prometheus.CounterOpts{ + Name: "log_messages", + Help: "Total number of log messages.", + }, []string{"level"}) + // Initialise counters for all supported levels: + for _, level := range supportedLevels { + counterVec.WithLabelValues(level.String()).Set(0) + } + // Try to register the counter vector: + err := prometheus.Register(counterVec) + if err != nil { + return nil, err + } + return &PrometheusHook{ + counterVec: counterVec, + }, nil +} + +// Fire increments the appropriate Prometheus counter depending on the entry's log level. +func (hook *PrometheusHook) Fire(entry *logrus.Entry) error { + hook.counterVec.WithLabelValues(entry.Level.String()).Inc() + return nil +} + +// Levels returns all supported log levels, i.e.: Debug, Info, Warn and Error, as +// there is no point incrementing a counter just before exiting/panicking. +func (hook *PrometheusHook) Levels() []logrus.Level { + return supportedLevels +} diff --git a/vendor/github.com/weaveworks/promrus/promrus_test.go b/vendor/github.com/weaveworks/promrus/promrus_test.go new file mode 100644 index 00000000000..fdc145e77a0 --- /dev/null +++ b/vendor/github.com/weaveworks/promrus/promrus_test.go @@ -0,0 +1,106 @@ +package promrus_test + +import ( + "fmt" + "io/ioutil" + "net" + "net/http" + "strconv" + "strings" + "testing" + + "github.com/Sirupsen/logrus" + log "github.com/Sirupsen/logrus" + "github.com/prometheus/client_golang/prometheus/promhttp" + "github.com/stretchr/testify/assert" + "github.com/weaveworks/promrus" +) + +const ( + addr string = ":8080" + endpoint string = "/metrics" +) + +func TestExposeAndQueryLogrusCounters(t *testing.T) { + // Create Prometheus hook and configure logrus to use it: + hook, err := promrus.NewPrometheusHook() + assert.Nil(t, err) + log.AddHook(hook) + log.SetLevel(logrus.DebugLevel) + + httpServePrometheusMetrics(t) + + lines := httpGetMetrics(t) + assert.Equal(t, 0, countFor(t, logrus.DebugLevel, lines)) + assert.Equal(t, 0, countFor(t, logrus.InfoLevel, lines)) + assert.Equal(t, 0, countFor(t, logrus.WarnLevel, lines)) + assert.Equal(t, 0, countFor(t, logrus.ErrorLevel, lines)) + + log.Debug("this is at debug level!") + lines = httpGetMetrics(t) + assert.Equal(t, 1, countFor(t, logrus.DebugLevel, lines)) + assert.Equal(t, 0, countFor(t, logrus.InfoLevel, lines)) + assert.Equal(t, 0, countFor(t, logrus.WarnLevel, lines)) + assert.Equal(t, 0, countFor(t, logrus.ErrorLevel, lines)) + + log.Info("this is at info level!") + lines = httpGetMetrics(t) + assert.Equal(t, 1, countFor(t, logrus.DebugLevel, lines)) + assert.Equal(t, 1, countFor(t, logrus.InfoLevel, lines)) + assert.Equal(t, 0, countFor(t, logrus.WarnLevel, lines)) + assert.Equal(t, 0, countFor(t, logrus.ErrorLevel, lines)) + + log.Warn("this is at warning level!") + lines = httpGetMetrics(t) + assert.Equal(t, 1, countFor(t, logrus.DebugLevel, lines)) + assert.Equal(t, 1, countFor(t, logrus.InfoLevel, lines)) + assert.Equal(t, 1, countFor(t, logrus.WarnLevel, lines)) + assert.Equal(t, 0, countFor(t, logrus.ErrorLevel, lines)) + + log.Error("this is at error level!") + lines = httpGetMetrics(t) + assert.Equal(t, 1, countFor(t, logrus.DebugLevel, lines)) + assert.Equal(t, 1, countFor(t, logrus.InfoLevel, lines)) + assert.Equal(t, 1, countFor(t, logrus.WarnLevel, lines)) + assert.Equal(t, 1, countFor(t, logrus.ErrorLevel, lines)) +} + +// httpServePrometheusMetrics exposes the Prometheus metrics over HTTP, in a different go routine. +func httpServePrometheusMetrics(t *testing.T) { + http.Handle(endpoint, promhttp.Handler()) + listener, err := net.Listen("tcp", addr) + assert.Nil(t, err) + go http.Serve(listener, nil) +} + +// httpGetMetrics queries the local HTTP server for the exposed metrics and parses the response. +func httpGetMetrics(t *testing.T) []string { + resp, err := http.Get(fmt.Sprintf("http://localhost%v%v", addr, endpoint)) + assert.Nil(t, err) + body, err := ioutil.ReadAll(resp.Body) + assert.Nil(t, err) + lines := strings.Split(string(body), "\n") + assert.True(t, len(lines) > 0) + return lines +} + +// countFor is a helper function to get the counter's value for the provided level. +func countFor(t *testing.T, level logrus.Level, lines []string) int { + // Metrics are exposed as per the below example: + // # HELP test_debug Number of log statements at debug level. + // # TYPE test_debug counter + // test_debug 0 + metric := fmt.Sprintf("log_messages{level=\"%v\"}", level) + for _, line := range lines { + items := strings.Split(line, " ") + if len(items) != 2 { // e.g. {"test_debug", "0"} + continue + } + if items[0] == metric { + count, err := strconv.ParseInt(items[1], 10, 32) + assert.Nil(t, err) + return int(count) + } + } + panic(fmt.Sprintf("Could not find %v in %v", metric, lines)) +}