From c8d3e48f3d67000d92caa6c136df2355aaff1bfe Mon Sep 17 00:00:00 2001 From: Ben Kochie Date: Mon, 3 Oct 2022 11:16:00 +0200 Subject: [PATCH] Refactor exporter config (#68) Switch exporter over to standard Prometheus exporter flags and logging. This eliminates the need for a configuraion file. Signed-off-by: SuperQ --- .golangci.yml | 10 +++ README.md | 48 +++++++------ go.mod | 18 ++++- go.sum | 22 ++++++ logging.go | 68 ------------------- main.go | 123 ++++++++++++++++++++++++++-------- options.go | 91 ------------------------- readjson.go | 60 +++++++++-------- scripts/errcheck_excludes.txt | 4 ++ smartctl.go | 26 ++++--- 10 files changed, 221 insertions(+), 249 deletions(-) create mode 100644 .golangci.yml delete mode 100644 logging.go delete mode 100644 options.go create mode 100644 scripts/errcheck_excludes.txt diff --git a/.golangci.yml b/.golangci.yml new file mode 100644 index 0000000..883547a --- /dev/null +++ b/.golangci.yml @@ -0,0 +1,10 @@ +--- +issues: + exclude-rules: + - path: _test.go + linters: + - errcheck + +linters-settings: + errcheck: + exclude: scripts/errcheck_excludes.txt diff --git a/README.md b/README.md index b838a5d..7db24b7 100644 --- a/README.md +++ b/README.md @@ -15,26 +15,34 @@ smartmontools >= 7.0, because export to json [released in 7.0](https://www.smart # Configuration ## Command line options -* `--config=/path/to/file.yaml`: Path to configuration file, default `/etc/smartctl_exporter.yaml` -* `--verbose`: verbosed log, default no -* `--debug`: Debug logging, default no -* `--version`: Show version and exit -## Configuration file -Example content: +The exporter will scan the system for available devices if no `--smartctl.device` flags are used. + ``` -smartctl_exporter: - bind_to: "[::1]:9633" - url_path: "/metrics" - fake_json: no - smartctl_location: /usr/sbin/smartctl - collect_not_more_than_period: 120s - devices: - - /dev/sda - - /dev/sdb - - /dev/sdc - - /dev/sdd - - /dev/sde - - /dev/sdf +usage: smartctl_exporter [] + +Flags: + -h, --help Show context-sensitive help (also try --help-long and --help-man). + --smartctl.path="/usr/sbin/smartctl" + The path to the smartctl binary + --smartctl.interval=60s The interval between smarctl polls + --smartctl.device=SMARTCTL.DEVICE ... + The device to monitor (repeatable) + --web.listen-address=":9633" + Address to listen on for web interface and telemetry + --web.telemetry-path="/metrics" + Path under which to expose metrics + --web.config.file="" [EXPERIMENTAL] Path to configuration file that can enable TLS or authentication. + --log.level=info Only log messages with the given severity or above. One of: [debug, info, warn, + error] + --log.format=logfmt Output format of log messages. One of: [logfmt, json] + --version Show application version. ``` -`fake_json` used for debugging. + +## TLS and basic authentication + +This exporter supports TLS and basic authentication. + +To use TLS and/or basic authentication, you need to pass a configuration file +using the `--web.config.file` parameter. The format of the file is described +[in the exporter-toolkit repository](https://github.com/prometheus/exporter-toolkit/blob/master/docs/web-configuration.md). diff --git a/go.mod b/go.mod index db7ecfc..0a3f5f2 100644 --- a/go.mod +++ b/go.mod @@ -3,21 +3,35 @@ module github.com/prometheus-community/smartctl_exporter go 1.17 require ( + github.com/go-kit/log v0.2.1 github.com/prometheus/client_golang v1.13.0 + github.com/prometheus/common v0.37.0 + github.com/prometheus/exporter-toolkit v0.7.1 github.com/tidwall/gjson v1.14.3 - gopkg.in/yaml.v2 v2.4.0 + gopkg.in/alecthomas/kingpin.v2 v2.2.6 ) require ( + github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 // indirect + github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/cespare/xxhash/v2 v2.1.2 // indirect + github.com/go-logfmt/logfmt v0.5.1 // indirect github.com/golang/protobuf v1.5.2 // indirect + github.com/jpillora/backoff v1.0.0 // indirect github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect + github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f // indirect + github.com/pkg/errors v0.9.1 // indirect github.com/prometheus/client_model v0.2.0 // indirect - github.com/prometheus/common v0.37.0 // indirect github.com/prometheus/procfs v0.8.0 // indirect github.com/tidwall/match v1.1.1 // indirect github.com/tidwall/pretty v1.2.0 // indirect + golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e // indirect + golang.org/x/net v0.0.0-20220225172249-27dd8689420f // indirect + golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b // indirect golang.org/x/sys v0.0.0-20220615213510-4f61da869c0c // indirect + golang.org/x/text v0.3.7 // indirect + google.golang.org/appengine v1.6.6 // indirect google.golang.org/protobuf v1.28.1 // indirect + gopkg.in/yaml.v2 v2.4.0 // indirect ) diff --git a/go.sum b/go.sum index 9c08c65..2a153e0 100644 --- a/go.sum +++ b/go.sum @@ -34,9 +34,11 @@ dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7 github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= +github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 h1:JYp7IbQjafoB+tBA3gMyHYHrpOtNuDiK/uB5uXxq5wM= 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/alecthomas/units v0.0.0-20190924025748-f65c72e2690d h1:UQZhZ2O0vMHr2cI+DC1Mbh0TJxzA3RcLoMsFw+aXw7E= github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= 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= @@ -52,6 +54,7 @@ github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMn github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= @@ -64,9 +67,12 @@ github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2 github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= github.com/go-kit/log v0.2.0/go.mod h1:NwTd00d/i8cPZ3xOwwiv2PO5MOcx78fFErGNcVmBjv0= +github.com/go-kit/log v0.2.1 h1:MRVx0/zhvdseW+Gza6N9rVzU/IVzaeE1SFI4raAhmBU= +github.com/go-kit/log v0.2.1/go.mod h1:NwTd00d/i8cPZ3xOwwiv2PO5MOcx78fFErGNcVmBjv0= 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-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= +github.com/go-logfmt/logfmt v0.5.1 h1:otpy5pqBCBZ1ng9RQ0dPu4PN7ba75Y/aA+UpowDyNVA= github.com/go-logfmt/logfmt v0.5.1/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs= 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= @@ -127,6 +133,7 @@ github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5m github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/jpillora/backoff v1.0.0 h1:uvFg412JmmHBHw7iwprIxkPMI+sGQ4kzOWsMeHnm2EA= github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= 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= @@ -153,10 +160,13 @@ github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lN github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= +github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f h1:KUppIJq7/+SVif2QVs3tOP0zanoHgBEVAwHxUSIzRqU= github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/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/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 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= @@ -173,9 +183,12 @@ github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6T github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo= github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc= +github.com/prometheus/common v0.29.0/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls= github.com/prometheus/common v0.32.1/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls= github.com/prometheus/common v0.37.0 h1:ccBbHCgIiT9uSoFY0vX8H3zsNR5eLt17/RQLUvn8pXE= github.com/prometheus/common v0.37.0/go.mod h1:phzohg0JFMnBEFGxTDbfu3QyL5GI8gTQJFhYO5B3mfA= +github.com/prometheus/exporter-toolkit v0.7.1 h1:c6RXaK8xBVercEeUQ4tRNL8UGWzDHfvj9dseo1FcK1Y= +github.com/prometheus/exporter-toolkit v0.7.1/go.mod h1:ZUBIj498ePooX9t/2xtDjeQYwvRpiPP2lh5u4iblj2g= 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/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= @@ -191,6 +204,7 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+ 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 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/tidwall/gjson v1.14.3 h1:9jvXn7olKEHU1S9vwoMGliaT8jq1vJ7IH/n9zD9Dnlw= github.com/tidwall/gjson v1.14.3/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= @@ -212,6 +226,8 @@ golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e h1:gsTQYXdTw2Gq7RBsWvlQ91b+aEQ6bXFUngBGuR8sPpI= +golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= @@ -270,8 +286,10 @@ golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/ golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= +golang.org/x/net v0.0.0-20220225172249-27dd8689420f h1:oA4XRj0qtSt8Yo1Zms0CUlsT3KG69V2UGQWPBxujDmc= golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -279,6 +297,7 @@ golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4Iltr golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b h1:clP8eMhB30EHdc0bd2Twtq6kgU7yl5ub2cQLSdrv1Dg= golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -339,6 +358,7 @@ golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3 golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= @@ -408,6 +428,7 @@ google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7 google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/appengine v1.6.6 h1:lMO5rYAqUxkmaj76jAkRUvt5JZgFymx/+Q5Mzfivuhc= google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= @@ -464,6 +485,7 @@ google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp0 google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w= google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +gopkg.in/alecthomas/kingpin.v2 v2.2.6 h1:jMFz6MfLP0/4fUyZle81rXUoxOBFi19VUFKVDOQfozc= 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-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/logging.go b/logging.go deleted file mode 100644 index 175f923..0000000 --- a/logging.go +++ /dev/null @@ -1,68 +0,0 @@ -// Copyright 2022 The Prometheus Authors -// 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. - -package main - -import ( - "fmt" -) - -// Logger is logger -type Logger struct { - verbose bool - debug bool -} - -func newLogger(verbose bool, debug bool) Logger { - logger := Logger{verbose: verbose, debug: debug} - return logger -} - -// Msg formatted message -func (i Logger) print(prefix string, format string, values ...interface{}) { - fmt.Printf(fmt.Sprintf("[%s] %s\n", prefix, format), values...) -} - -// Info log message -func (i Logger) Info(format string, values ...interface{}) { - i.print("Info", format, values...) -} - -// Warning log message -func (i Logger) Warning(format string, values ...interface{}) { - i.print("Warning", format, values...) -} - -// Error log message -func (i Logger) Error(format string, values ...interface{}) { - i.print("Error", format, values...) -} - -// Panic log message -func (i Logger) Panic(format string, values ...interface{}) { - i.print("Panic", format, values...) -} - -// Verbose log message -func (i Logger) Verbose(format string, values ...interface{}) { - if i.verbose { - i.print("Verbose", format, values...) - } -} - -// Debug log message -func (i Logger) Debug(format string, values ...interface{}) { - if i.debug { - i.print("Debug", format, values...) - } -} diff --git a/main.go b/main.go index 7e26058..32bb7a5 100644 --- a/main.go +++ b/main.go @@ -14,21 +14,30 @@ package main import ( - "log" "net/http" + "os" + "time" + "github.com/go-kit/log" + "github.com/go-kit/log/level" "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus/collectors" "github.com/prometheus/client_golang/prometheus/promhttp" -) - -var ( - options Options - logger Logger + "github.com/prometheus/common/promlog" + "github.com/prometheus/common/promlog/flag" + "github.com/prometheus/common/version" + "github.com/prometheus/exporter-toolkit/web" + webflag "github.com/prometheus/exporter-toolkit/web/kingpinflag" + kingpin "gopkg.in/alecthomas/kingpin.v2" ) // SMARTctlManagerCollector implements the Collector interface. type SMARTctlManagerCollector struct { + CollectPeriod string + CollectPeriodDuration time.Duration + Devices []string + + logger log.Logger } // Describe sends the super-set of all possible descriptors of metrics @@ -39,43 +48,101 @@ func (i SMARTctlManagerCollector) Describe(ch chan<- *prometheus.Desc) { // Collect is called by the Prometheus registry when collecting metrics. func (i SMARTctlManagerCollector) Collect(ch chan<- prometheus.Metric) { info := NewSMARTctlInfo(ch) - for _, device := range options.SMARTctl.Devices { - if json, err := readData(device); err == nil { + for _, device := range i.Devices { + if json, err := readData(i.logger, device); err == nil { info.SetJSON(json) - smart := NewSMARTctl(json, ch) + smart := NewSMARTctl(i.logger, json, ch) smart.Collect() } else { - logger.Error(err.Error()) + level.Error(i.logger).Log("msg", "Error collecting SMART data", "err", err.Error()) } } info.Collect() } -func init() { - options = loadOptions() - - if len(options.SMARTctl.Devices) == 0 { - logger.Debug("No devices specified, trying to load them automatically") - json := readSMARTctlDevices() - devices := json.Get("devices").Array() - for _, d := range devices { - device := d.Get("name").String() - logger.Debug("Found device: %s", device) - options.SMARTctl.Devices = append(options.SMARTctl.Devices, device) - } - } -} +var ( + smartctlPath = kingpin.Flag("smartctl.path", + "The path to the smartctl binary", + ).Default("/usr/sbin/smartctl").String() + smartctlInterval = kingpin.Flag("smartctl.interval", + "The interval between smarctl polls", + ).Default("60s").Duration() + smartctlDevices = kingpin.Flag("smartctl.device", + "The device to monitor (repeatable)", + ).Strings() + smartctlFakeData = kingpin.Flag("smartctl.fake-data", + "The device to monitor (repeatable)", + ).Default("false").Hidden().Bool() +) func main() { + listenAddress := kingpin.Flag("web.listen-address", + "Address to listen on for web interface and telemetry", + ).Default(":9633").String() + metricsPath := kingpin.Flag( + "web.telemetry-path", "Path under which to expose metrics", + ).Default("/metrics").String() + webConfig := webflag.AddFlags(kingpin.CommandLine) + + promlogConfig := &promlog.Config{} + flag.AddFlags(kingpin.CommandLine, promlogConfig) + kingpin.Version(version.Print("smartctl_exporter")) + kingpin.HelpFlag.Short('h') + kingpin.Parse() + logger := promlog.New(promlogConfig) + + level.Info(logger).Log("msg", "Starting systemd_exporter", "version", version.Info()) + level.Info(logger).Log("msg", "Build context", "build_context", version.BuildContext()) + + devices := *smartctlDevices + + if len(devices) == 0 { + level.Info(logger).Log("msg", "No devices specified, trying to load them automatically") + json := readSMARTctlDevices(logger) + scannedDevices := json.Get("devices").Array() + for _, d := range scannedDevices { + device := d.Get("name").String() + level.Info(logger).Log("msg", "Found device", "device", device) + devices = append(devices, device) + } + } + + if len(devices) == 0 { + level.Error(logger).Log("msg", "No devices found") + os.Exit(1) + } + + collector := SMARTctlManagerCollector{ + Devices: devices, + logger: logger, + } + reg := prometheus.NewPedanticRegistry() reg.MustRegister( collectors.NewProcessCollector(collectors.ProcessCollectorOpts{}), collectors.NewGoCollector(), ) - prometheus.WrapRegistererWithPrefix("", reg).MustRegister(SMARTctlManagerCollector{}) + prometheus.WrapRegistererWithPrefix("", reg).MustRegister(collector) - logger.Info("Starting on %s%s", options.SMARTctl.BindTo, options.SMARTctl.URLPath) - http.Handle(options.SMARTctl.URLPath, promhttp.HandlerFor(reg, promhttp.HandlerOpts{})) - log.Fatal(http.ListenAndServe(options.SMARTctl.BindTo, nil)) + level.Info(logger).Log("msg", "Listening on", "address", *listenAddress) + http.Handle(*metricsPath, promhttp.HandlerFor(reg, promhttp.HandlerOpts{})) + http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { + _, err := w.Write([]byte(` + Smartctl Exporter + +

Smartctl Exporter

+

Metrics

+ + `)) + if err != nil { + level.Error(logger).Log("msg", "Couldn't write response", "err", err) + } + }) + + srv := &http.Server{Addr: *listenAddress} + if err := web.ListenAndServe(srv, *webConfig, logger); err != nil { + level.Error(logger).Log("err", err) + os.Exit(1) + } } diff --git a/options.go b/options.go deleted file mode 100644 index dcb6e46..0000000 --- a/options.go +++ /dev/null @@ -1,91 +0,0 @@ -// Copyright 2022 The Prometheus Authors -// 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. - -package main - -import ( - "flag" - "fmt" - "io/ioutil" - "os" - "time" - - yaml "gopkg.in/yaml.v2" -) - -var ( - exporterVersion = "0.5" -) - -// SMARTOptions is a inner representation of a options -type SMARTOptions struct { - BindTo string `yaml:"bind_to"` - URLPath string `yaml:"url_path"` - FakeJSON bool `yaml:"fake_json"` - SMARTctlLocation string `yaml:"smartctl_location"` - CollectPeriod string `yaml:"collect_not_more_than_period"` - CollectPeriodDuration time.Duration - Devices []string `yaml:"devices"` -} - -// Options is a representation of a options -type Options struct { - SMARTctl SMARTOptions `yaml:"smartctl_exporter"` -} - -// Parse options from yaml config file -func loadOptions() Options { - configFile := flag.String("config", "/etc/smartctl_exporter.yaml", "Path to smartctl_exporter config file") - verbose := flag.Bool("verbose", false, "Verbose log output") - debug := flag.Bool("debug", false, "Debug log output") - version := flag.Bool("version", false, "Show application version and exit") - flag.Parse() - - if *version { - fmt.Printf("smartctl_exporter version: %s\n", exporterVersion) - os.Exit(0) - } - - logger = newLogger(*verbose, *debug) - - logger.Verbose("Read options from %s\n", *configFile) - yamlFile, err := ioutil.ReadFile(*configFile) - if err != nil { - logger.Panic("Failed read %s: %s", configFile, err) - } - - opts := Options{ - SMARTOptions{ - BindTo: "9633", - URLPath: "/metrics", - FakeJSON: false, - SMARTctlLocation: "/usr/sbin/smartctl", - CollectPeriod: "60s", - Devices: []string{}, - }, - } - - if yaml.Unmarshal(yamlFile, &opts) != nil { - logger.Panic("Failed parse %s: %s", configFile, err) - } - - d, err := time.ParseDuration(opts.SMARTctl.CollectPeriod) - if err != nil { - logger.Panic("Failed read collect_not_more_than_period (%s): %s", opts.SMARTctl.CollectPeriod, err) - } - - opts.SMARTctl.CollectPeriodDuration = d - - logger.Debug("Parsed options: %s", opts) - return opts -} diff --git a/readjson.go b/readjson.go index 8e6682e..9977899 100644 --- a/readjson.go +++ b/readjson.go @@ -21,6 +21,8 @@ import ( "strings" "time" + "github.com/go-kit/log" + "github.com/go-kit/log/level" "github.com/tidwall/gjson" ) @@ -47,50 +49,50 @@ func parseJSON(data string) gjson.Result { } // Reading fake smartctl json -func readFakeSMARTctl(device string) gjson.Result { +func readFakeSMARTctl(logger log.Logger, device string) gjson.Result { s := strings.Split(device, "/") filename := fmt.Sprintf("debug/%s.json", s[len(s)-1]) - logger.Verbose("Read fake S.M.A.R.T. data from json: %s", filename) + level.Debug(logger).Log("msg", "Read fake S.M.A.R.T. data from json", "filename", filename) jsonFile, err := ioutil.ReadFile(filename) if err != nil { - logger.Error("Fake S.M.A.R.T. data reading error: %s", err) + level.Error(logger).Log("msg", "Fake S.M.A.R.T. data reading error", "err", err) return parseJSON("{}") } return parseJSON(string(jsonFile)) } // Get json from smartctl and parse it -func readSMARTctl(device string) (gjson.Result, bool) { - logger.Debug("Collecting S.M.A.R.T. counters, device: %s", device) - out, err := exec.Command(options.SMARTctl.SMARTctlLocation, "--json", "--xall", device).Output() +func readSMARTctl(logger log.Logger, device string) (gjson.Result, bool) { + level.Debug(logger).Log("msg", "Collecting S.M.A.R.T. counters", "device", device) + out, err := exec.Command(*smartctlPath, "--json", "--xall", device).Output() if err != nil { - logger.Warning("S.M.A.R.T. output reading error: %s", err) + level.Warn(logger).Log("msg", "S.M.A.R.T. output reading", "err", err) } json := parseJSON(string(out)) - rcOk := resultCodeIsOk(json.Get("smartctl.exit_status").Int()) - jsonOk := jsonIsOk(json) + rcOk := resultCodeIsOk(logger, json.Get("smartctl.exit_status").Int()) + jsonOk := jsonIsOk(logger, json) return json, rcOk && jsonOk } -func readSMARTctlDevices() gjson.Result { - logger.Debug("Collecting devices") - out, err := exec.Command(options.SMARTctl.SMARTctlLocation, "--json", "--scan-open").Output() +func readSMARTctlDevices(logger log.Logger) gjson.Result { + level.Debug(logger).Log("msg", "Collecting devices") + out, err := exec.Command(*smartctlPath, "--json", "--scan-open").Output() if err != nil { - logger.Warning("S.M.A.R.T. output reading error: %s", err) + level.Warn(logger).Log("msg", "S.M.A.R.T. output reading error", "err", err) } return parseJSON(string(out)) } // Select json source and parse -func readData(device string) (gjson.Result, error) { - if options.SMARTctl.FakeJSON { - return readFakeSMARTctl(device), nil +func readData(logger log.Logger, device string) (gjson.Result, error) { + if *smartctlFakeData { + return readFakeSMARTctl(logger, device), nil } if _, err := os.Stat(device); err == nil { cacheValue, cacheOk := jsonCache[device] - if !cacheOk || time.Now().After(cacheValue.LastCollect.Add(options.SMARTctl.CollectPeriodDuration)) { - json, ok := readSMARTctl(device) + if !cacheOk || time.Now().After(cacheValue.LastCollect.Add(*smartctlInterval)) { + json, ok := readSMARTctl(logger, device) if ok { jsonCache[device] = JSONCache{JSON: json, LastCollect: time.Now()} return jsonCache[device].JSON, nil @@ -103,48 +105,48 @@ func readData(device string) (gjson.Result, error) { } // Parse smartctl return code -func resultCodeIsOk(SMARTCtlResult int64) bool { +func resultCodeIsOk(logger log.Logger, SMARTCtlResult int64) bool { result := true if SMARTCtlResult > 0 { b := SMARTCtlResult if (b & 1) != 0 { - logger.Error("Command line did not parse.") + level.Error(logger).Log("msg", "Command line did not parse.") result = false } if (b & (1 << 1)) != 0 { - logger.Error("Device open failed, device did not return an IDENTIFY DEVICE structure, or device is in a low-power mode") + level.Error(logger).Log("msg", "Device open failed, device did not return an IDENTIFY DEVICE structure, or device is in a low-power mode") result = false } if (b & (1 << 2)) != 0 { - logger.Warning("Some SMART or other ATA command to the disk failed, or there was a checksum error in a SMART data structure") + level.Warn(logger).Log("msg", "Some SMART or other ATA command to the disk failed, or there was a checksum error in a SMART data structure") } if (b & (1 << 3)) != 0 { - logger.Warning("SMART status check returned 'DISK FAILING'.") + level.Warn(logger).Log("msg", "SMART status check returned 'DISK FAILING'.") } if (b & (1 << 4)) != 0 { - logger.Warning("We found prefail Attributes <= threshold.") + level.Warn(logger).Log("msg", "We found prefail Attributes <= threshold.") } if (b & (1 << 5)) != 0 { - logger.Warning("SMART status check returned 'DISK OK' but we found that some (usage or prefail) Attributes have been <= threshold at some time in the past.") + level.Warn(logger).Log("msg", "SMART status check returned 'DISK OK' but we found that some (usage or prefail) Attributes have been <= threshold at some time in the past.") } if (b & (1 << 6)) != 0 { - logger.Warning("The device error log contains records of errors.") + level.Warn(logger).Log("msg", "The device error log contains records of errors.") } if (b & (1 << 7)) != 0 { - logger.Warning("The device self-test log contains records of errors. [ATA only] Failed self-tests outdated by a newer successful extended self-test are ignored.") + level.Warn(logger).Log("msg", "The device self-test log contains records of errors. [ATA only] Failed self-tests outdated by a newer successful extended self-test are ignored.") } } return result } // Check json -func jsonIsOk(json gjson.Result) bool { +func jsonIsOk(logger log.Logger, json gjson.Result) bool { messages := json.Get("smartctl.messages") // logger.Debug(messages.String()) if messages.Exists() { for _, message := range messages.Array() { if message.Get("severity").String() == "error" { - logger.Error(message.Get("string").String()) + level.Error(logger).Log("msg", message.Get("string").String()) return false } } diff --git a/scripts/errcheck_excludes.txt b/scripts/errcheck_excludes.txt new file mode 100644 index 0000000..14b824f --- /dev/null +++ b/scripts/errcheck_excludes.txt @@ -0,0 +1,4 @@ +// Used in HTTP handlers, any error is handled by the server itself. +(net/http.ResponseWriter).Write +// Never check for logger errors. +(github.com/go-kit/log.Logger).Log diff --git a/smartctl.go b/smartctl.go index ca40a7d..a40a03a 100644 --- a/smartctl.go +++ b/smartctl.go @@ -17,6 +17,8 @@ import ( "fmt" "strings" + "github.com/go-kit/log" + "github.com/go-kit/log/level" "github.com/prometheus/client_golang/prometheus" "github.com/tidwall/gjson" ) @@ -33,26 +35,28 @@ type SMARTDevice struct { type SMARTctl struct { ch chan<- prometheus.Metric json gjson.Result + logger log.Logger device SMARTDevice } // NewSMARTctl is smartctl constructor -func NewSMARTctl(json gjson.Result, ch chan<- prometheus.Metric) SMARTctl { - smart := SMARTctl{} - smart.ch = ch - smart.json = json - smart.device = SMARTDevice{ - device: strings.TrimSpace(smart.json.Get("device.name").String()), - serial: strings.TrimSpace(smart.json.Get("serial_number").String()), - family: strings.TrimSpace(smart.json.Get("model_family").String()), - model: strings.TrimSpace(smart.json.Get("model_name").String()), +func NewSMARTctl(logger log.Logger, json gjson.Result, ch chan<- prometheus.Metric) SMARTctl { + return SMARTctl{ + ch: ch, + json: json, + logger: logger, + device: SMARTDevice{ + device: strings.TrimSpace(json.Get("device.name").String()), + serial: strings.TrimSpace(json.Get("serial_number").String()), + family: strings.TrimSpace(json.Get("model_family").String()), + model: strings.TrimSpace(json.Get("model_name").String()), + }, } - return smart } // Collect metrics func (smart *SMARTctl) Collect() { - logger.Verbose("Collecting metrics from %s: %s, %s", smart.device.device, smart.device.family, smart.device.model) + level.Debug(smart.logger).Log("msg", "Collecting metrics from", "device", smart.device.device, "family", smart.device.family, "model", smart.device.model) smart.mineExitStatus() smart.mineDevice() smart.mineCapacity()