summarylogtreecommitdiffstats
path: root/290.patch
blob: 70573a65cdea91872ffd4250307d5da045bcb21c (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
From 06cb7f46f6b4d47d37ff2cababd1b3bc818c8149 Mon Sep 17 00:00:00 2001
From: Konstantin Shalygin <k0ste@k0ste.ru>
Date: Fri, 25 Apr 2025 16:00:24 +0300
Subject: [PATCH] cleanup: better logging for device & type, use Kingpin to
 check option values

* use kingpin to check `smartctl.powermode-check` option value
* better help message for `smartctl.powermode-check` with possible values
* better logging labels, with device name & type, without device path

Signed-off-by: Konstantin Shalygin <k0ste@k0ste.ru>
---
 main.go     | 34 ++++++++++------------------------
 readjson.go | 23 +++++++++++------------
 2 files changed, 21 insertions(+), 36 deletions(-)

diff --git a/main.go b/main.go
index c5b138a..09a9e34 100644
--- a/main.go
+++ b/main.go
@@ -14,7 +14,6 @@
 package main
 
 import (
-	"fmt"
 	"log/slog"
 	"net/http"
 	"os"
@@ -102,30 +101,31 @@ var (
 		"The interval between smartctl polls",
 	).Default("60s").Duration()
 	smartctlRescanInterval = kingpin.Flag("smartctl.rescan",
-		"The interval between rescanning for new/disappeared devices. If the interval is smaller than 1s no rescanning takes place. If any devices are configured with smartctl.device also no rescanning takes place.",
+		"The interval between rescanning for new/disappeared devices. If the interval is smaller than 1s no rescanning takes place. If any devices are configured with smartctl.device also no rescanning takes place",
 	).Default("10m").Duration()
-	smartctlScan    = kingpin.Flag("smartctl.scan", "Enable scanning. This is a default if no devices are specified").Default("false").Bool()
+	smartctlScan = kingpin.Flag("smartctl.scan", "Enable scanning. This is a default if no devices are specified",
+	).Default("false").Bool()
 	smartctlDevices = kingpin.Flag("smartctl.device",
 		"The device to monitor. Device type can be specified after a semicolon, eg. '/dev/bus/0;megaraid,1' (repeatable)",
 	).Strings()
 	smartctlDeviceExclude = kingpin.Flag(
 		"smartctl.device-exclude",
-		"Regexp of devices to exclude from automatic scanning. (mutually exclusive to device-include)",
+		"Regexp of devices to exclude from automatic scanning (mutually exclusive to device-include)",
 	).Default("").String()
 	smartctlDeviceInclude = kingpin.Flag(
 		"smartctl.device-include",
-		"Regexp of devices to exclude from automatic scanning. (mutually exclusive to device-exclude)",
+		"Regexp of devices to exclude from automatic scanning (mutually exclusive to device-exclude)",
 	).Default("").String()
 	smartctlScanDeviceTypes = kingpin.Flag(
 		"smartctl.scan-device-type",
-		"Device type to use during automatic scan. Special by-id value forces predictable device names. (repeatable)",
+		"Device type to use during automatic scan. Special by-id value forces predictable device names (repeatable)",
 	).Strings()
 	smartctlFakeData = kingpin.Flag("smartctl.fake-data",
 		"The device to monitor (repeatable)",
 	).Default("false").Hidden().Bool()
 	smartctlPowerModeCheck = kingpin.Flag("smartctl.powermode-check",
-		"Whether or not to check powermode before fetching data",
-	).Default("standby").String()
+		"Whether or not to check powermode before fetching data. Must be one of: 'never', 'sleep', 'standby', 'idle'. Default is 'standby'",
+	).Default("standby").Enum("never", "sleep", "standby", "idle")
 )
 
 // scanDevices uses smartctl to gather the list of available devices.
@@ -177,15 +177,6 @@ func buildDevicesFromFlag(devices []Device) []Device {
 	return devices
 }
 
-func validatePowerMode(mode string) error {
-	switch strings.ToLower(mode) {
-	case "never", "sleep", "standby", "idle":
-		return nil
-	default:
-		return fmt.Errorf("invalid power mode: %s. Must be one of: never, sleep, standby, idle", mode)
-	}
-}
-
 func main() {
 	metricsPath := kingpin.Flag(
 		"web.telemetry-path", "Path under which to expose metrics",
@@ -198,11 +189,6 @@ func main() {
 	kingpin.HelpFlag.Short('h')
 	kingpin.Parse()
 	logger := promslog.New(promslogConfig)
-
-	if err := validatePowerMode(*smartctlPowerModeCheck); err != nil {
-		logger.Error(err.Error())
-		os.Exit(1)
-	}
 	logger.Info("Starting smartctl_exporter", "version", version.Info())
 	logger.Info("Build context", "build_context", version.BuildContext())
 	var devices []Device
@@ -258,7 +244,7 @@ func main() {
 		}
 		landingPage, err := web.NewLandingPage(landingConfig)
 		if err != nil {
-			logger.Error("error creating landing page", "err", err)
+			logger.Error("Error creating landing page", "err", err)
 			os.Exit(1)
 		}
 		http.Handle("/", landingPage)
@@ -266,7 +252,7 @@ func main() {
 
 	srv := &http.Server{}
 	if err := web.ListenAndServe(srv, toolkitFlags, logger); err != nil {
-		logger.Error("error running HTTP server", "err", err)
+		logger.Error("Error running HTTP server", "err", err)
 		os.Exit(1)
 	}
 }
diff --git a/readjson.go b/readjson.go
index a03b1d6..f2737a7 100644
--- a/readjson.go
+++ b/readjson.go
@@ -69,14 +69,14 @@ func readSMARTctl(logger *slog.Logger, device Device, wg *sync.WaitGroup) {
 	logger.Debug("Calling smartctl with args", "args", strings.Join(smartctlArgs, " "))
 	out, err := exec.Command(*smartctlPath, smartctlArgs...).Output()
 	if err != nil {
-		logger.Warn("S.M.A.R.T. output reading", "err", err, "device", device)
+		logger.Warn("S.M.A.R.T. output reading", "device", device.Label, "type", device.Type, "err", err)
 	}
 	// Accommodate a smartmontools pre-7.3 bug
 	cleaned_out := strings.TrimPrefix(string(out), "  Pending defect count:")
 	json := parseJSON(cleaned_out)
 	rcOk := resultCodeIsOk(logger, device, json.Get("smartctl.exit_status").Int())
 	jsonOk := jsonIsOk(logger, json)
-	logger.Debug("Collected S.M.A.R.T. json data", "device", device, "duration", time.Since(start))
+	logger.Debug("Collected S.M.A.R.T. json data", "device", device.Label, "type", device.Type, "duration", time.Since(start))
 	if rcOk && jsonOk {
 		jsonCache.Store(device, JSONCache{JSON: json, LastCollect: time.Now()})
 	}
@@ -127,7 +127,7 @@ func readData(logger *slog.Logger, device Device) gjson.Result {
 
 	cacheValue, found := jsonCache.Load(device)
 	if !found {
-		logger.Warn("device not found", "device", device)
+		logger.Warn("Device not found", "device", device.Label, "type", device.Type)
 		return gjson.Result{}
 	}
 	return cacheValue.(JSONCache).JSON
@@ -139,30 +139,30 @@ func resultCodeIsOk(logger *slog.Logger, device Device, SMARTCtlResult int64) bo
 	if SMARTCtlResult > 0 {
 		b := SMARTCtlResult
 		if (b & 1) != 0 {
-			logger.Error("Command line did not parse", "device", device)
+			logger.Error("Command line did not parse", "device", device.Label, "type", device.Type)
 			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", "device", device)
+			logger.Error("Device open failed, device did not return an IDENTIFY DEVICE structure, or device is in a low-power mode", "device", device.Label, "type", device.Type)
 			result = false
 		}
 		if (b & (1 << 2)) != 0 {
-			logger.Warn("Some SMART or other ATA command to the disk failed, or there was a checksum error in a SMART data structure", "device", device)
+			logger.Warn("Some SMART or other ATA command to the disk failed, or there was a checksum error in a SMART data structure", "device", device.Label, "type", device.Type)
 		}
 		if (b & (1 << 3)) != 0 {
-			logger.Warn("SMART status check returned 'DISK FAILING'", "device", device)
+			logger.Warn("SMART status check returned 'DISK FAILING'", "device", device.Label, "type", device.Type)
 		}
 		if (b & (1 << 4)) != 0 {
-			logger.Warn("We found prefail Attributes <= threshold", "device", device)
+			logger.Warn("We found prefail Attributes <= threshold", "device", device.Label, "type", device.Type)
 		}
 		if (b & (1 << 5)) != 0 {
-			logger.Warn("SMART status check returned 'DISK OK' but we found that some (usage or prefail) Attributes have been <= threshold at some time in the past", "device", device)
+			logger.Warn("SMART status check returned 'DISK OK' but we found that some (usage or prefail) Attributes have been <= threshold at some time in the past", "device", device.Label, "type", device.Type)
 		}
 		if (b & (1 << 6)) != 0 {
-			logger.Warn("The device error log contains records of errors", "device", device)
+			logger.Warn("The device error log contains records of errors", "device", device.Label, "type", device.Type)
 		}
 		if (b & (1 << 7)) != 0 {
-			logger.Warn("The device self-test log contains records of errors. [ATA only] Failed self-tests outdated by a newer successful extended self-test are ignored", "device", device)
+			logger.Warn("The device self-test log contains records of errors. [ATA only] Failed self-tests outdated by a newer successful extended self-test are ignored", "device", device.Label, "type", device.Type)
 		}
 	}
 	return result
@@ -171,7 +171,6 @@ func resultCodeIsOk(logger *slog.Logger, device Device, SMARTCtlResult int64) bo
 // Check json
 func jsonIsOk(logger *slog.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" {