Browse code

Merge pull request #33845 from cyphar/devicemapper-show-me-your-logs

devicemapper: rework logging and add --storage-opt dm.libdm_log_level

Sebastiaan van Stijn authored on 2017/07/13 09:46:14
Showing 4 changed files
... ...
@@ -34,17 +34,13 @@ import (
34 34
 )
35 35
 
36 36
 var (
37
-	defaultDataLoopbackSize     int64  = 100 * 1024 * 1024 * 1024
38
-	defaultMetaDataLoopbackSize int64  = 2 * 1024 * 1024 * 1024
39
-	defaultBaseFsSize           uint64 = 10 * 1024 * 1024 * 1024
40
-	defaultThinpBlockSize       uint32 = 128 // 64K = 128 512b sectors
41
-	defaultUdevSyncOverride            = false
42
-	maxDeviceID                        = 0xffffff // 24 bit, pool limit
43
-	deviceIDMapSz                      = (maxDeviceID + 1) / 8
44
-	// We retry device removal so many a times that even error messages
45
-	// will fill up console during normal operation. So only log Fatal
46
-	// messages by default.
47
-	logLevel                            = devicemapper.LogLevelFatal
37
+	defaultDataLoopbackSize      int64  = 100 * 1024 * 1024 * 1024
38
+	defaultMetaDataLoopbackSize  int64  = 2 * 1024 * 1024 * 1024
39
+	defaultBaseFsSize            uint64 = 10 * 1024 * 1024 * 1024
40
+	defaultThinpBlockSize        uint32 = 128 // 64K = 128 512b sectors
41
+	defaultUdevSyncOverride             = false
42
+	maxDeviceID                         = 0xffffff // 24 bit, pool limit
43
+	deviceIDMapSz                       = (maxDeviceID + 1) / 8
48 44
 	driverDeferredRemovalSupport        = false
49 45
 	enableDeferredRemoval               = false
50 46
 	enableDeferredDeletion              = false
... ...
@@ -1272,26 +1268,6 @@ func setCloseOnExec(name string) {
1272 1272
 	}
1273 1273
 }
1274 1274
 
1275
-// DMLog implements logging using DevMapperLogger interface.
1276
-func (devices *DeviceSet) DMLog(level int, file string, line int, dmError int, message string) {
1277
-	// By default libdm sends us all the messages including debug ones.
1278
-	// We need to filter out messages here and figure out which one
1279
-	// should be printed.
1280
-	if level > logLevel {
1281
-		return
1282
-	}
1283
-
1284
-	// FIXME(vbatts) push this back into ./pkg/devicemapper/
1285
-	if level <= devicemapper.LogLevelErr {
1286
-		logrus.Errorf("libdevmapper(%d): %s:%d (%d) %s", level, file, line, dmError, message)
1287
-	} else if level <= devicemapper.LogLevelInfo {
1288
-		logrus.Infof("libdevmapper(%d): %s:%d (%d) %s", level, file, line, dmError, message)
1289
-	} else {
1290
-		// FIXME(vbatts) push this back into ./pkg/devicemapper/
1291
-		logrus.Debugf("libdevmapper(%d): %s:%d (%d) %s", level, file, line, dmError, message)
1292
-	}
1293
-}
1294
-
1295 1275
 func major(device uint64) uint64 {
1296 1276
 	return (device >> 8) & 0xfff
1297 1277
 }
... ...
@@ -1689,9 +1665,6 @@ func (devices *DeviceSet) enableDeferredRemovalDeletion() error {
1689 1689
 }
1690 1690
 
1691 1691
 func (devices *DeviceSet) initDevmapper(doInit bool) (retErr error) {
1692
-	// give ourselves to libdm as a log handler
1693
-	devicemapper.LogInit(devices)
1694
-
1695 1692
 	if err := devices.enableDeferredRemovalDeletion(); err != nil {
1696 1693
 		return err
1697 1694
 	}
... ...
@@ -2804,6 +2777,18 @@ func NewDeviceSet(root string, doInit bool, options []string, uidMaps, gidMaps [
2804 2804
 				return nil, errors.New("dm.thinp_autoextend_threshold must be greater than 0 and less than 100")
2805 2805
 			}
2806 2806
 			lvmSetupConfig.AutoExtendThreshold = per
2807
+		case "dm.libdm_log_level":
2808
+			level, err := strconv.ParseInt(val, 10, 32)
2809
+			if err != nil {
2810
+				return nil, errors.Wrapf(err, "could not parse `dm.libdm_log_level=%s`", val)
2811
+			}
2812
+			if level < devicemapper.LogLevelFatal || level > devicemapper.LogLevelDebug {
2813
+				return nil, errors.Errorf("dm.libdm_log_level must be in range [%d,%d]", devicemapper.LogLevelFatal, devicemapper.LogLevelDebug)
2814
+			}
2815
+			// Register a new logging callback with the specified level.
2816
+			devicemapper.LogInit(devicemapper.DefaultLogger{
2817
+				Level: int(level),
2818
+			})
2807 2819
 		default:
2808 2820
 			return nil, fmt.Errorf("devmapper: Unknown option %s\n", key)
2809 2821
 		}
... ...
@@ -13,11 +13,6 @@ import (
13 13
 	"golang.org/x/sys/unix"
14 14
 )
15 15
 
16
-// DevmapperLogger defines methods for logging with devicemapper.
17
-type DevmapperLogger interface {
18
-	DMLog(level int, file string, line int, dmError int, message string)
19
-}
20
-
21 16
 const (
22 17
 	deviceCreate TaskType = iota
23 18
 	deviceReload
... ...
@@ -264,19 +259,6 @@ func UdevWait(cookie *uint) error {
264 264
 	return nil
265 265
 }
266 266
 
267
-// LogInitVerbose is an interface to initialize the verbose logger for the device mapper library.
268
-func LogInitVerbose(level int) {
269
-	DmLogInitVerbose(level)
270
-}
271
-
272
-var dmLogger DevmapperLogger
273
-
274
-// LogInit initializes the logger for the device mapper library.
275
-func LogInit(logger DevmapperLogger) {
276
-	dmLogger = logger
277
-	LogWithErrnoInit()
278
-}
279
-
280 267
 // SetDevDir sets the dev folder for the device mapper library (usually /dev).
281 268
 func SetDevDir(dir string) error {
282 269
 	if res := DmSetDevDir(dir); res != 1 {
... ...
@@ -5,17 +5,45 @@ package devicemapper
5 5
 import "C"
6 6
 
7 7
 import (
8
+	"fmt"
8 9
 	"strings"
10
+
11
+	"github.com/Sirupsen/logrus"
9 12
 )
10 13
 
14
+// DevmapperLogger defines methods required to register as a callback for
15
+// logging events recieved from devicemapper. Note that devicemapper will send
16
+// *all* logs regardless to callbacks (including debug logs) so it's
17
+// recommended to not spam the console with the outputs.
18
+type DevmapperLogger interface {
19
+	// DMLog is the logging callback containing all of the information from
20
+	// devicemapper. The interface is identical to the C libdm counterpart.
21
+	DMLog(level int, file string, line int, dmError int, message string)
22
+}
23
+
24
+// dmLogger is the current logger in use that is being forwarded our messages.
25
+var dmLogger DevmapperLogger
26
+
27
+// LogInit changes the logging callback called after processing libdm logs for
28
+// error message information. The default logger simply forwards all logs to
29
+// logrus. Calling LogInit(nil) disables the calling of callbacks.
30
+func LogInit(logger DevmapperLogger) {
31
+	dmLogger = logger
32
+}
33
+
11 34
 // Due to the way cgo works this has to be in a separate file, as devmapper.go has
12 35
 // definitions in the cgo block, which is incompatible with using "//export"
13 36
 
14
-// DevmapperLogCallback exports the devmapper log callback for cgo.
37
+// DevmapperLogCallback exports the devmapper log callback for cgo. Note that
38
+// because we are using callbacks, this function will be called for *every* log
39
+// in libdm (even debug ones because there's no way of setting the verbosity
40
+// level for an external logging callback).
15 41
 //export DevmapperLogCallback
16
-func DevmapperLogCallback(level C.int, file *C.char, line C.int, dmErrnoOrClass C.int, message *C.char) {
42
+func DevmapperLogCallback(level C.int, file *C.char, line, dmErrnoOrClass C.int, message *C.char) {
17 43
 	msg := C.GoString(message)
18
-	if level < 7 {
44
+
45
+	// Track what errno libdm saw, because the library only gives us 0 or 1.
46
+	if level < LogLevelDebug {
19 47
 		if strings.Contains(msg, "busy") {
20 48
 			dmSawBusy = true
21 49
 		}
... ...
@@ -33,3 +61,61 @@ func DevmapperLogCallback(level C.int, file *C.char, line C.int, dmErrnoOrClass
33 33
 		dmLogger.DMLog(int(level), C.GoString(file), int(line), int(dmErrnoOrClass), msg)
34 34
 	}
35 35
 }
36
+
37
+// DefaultLogger is the default logger used by pkg/devicemapper. It forwards
38
+// all logs that are of higher or equal priority to the given level to the
39
+// corresponding logrus level.
40
+type DefaultLogger struct {
41
+	// Level corresponds to the highest libdm level that will be forwarded to
42
+	// logrus. In order to change this, register a new DefaultLogger.
43
+	Level int
44
+}
45
+
46
+// DMLog is the logging callback containing all of the information from
47
+// devicemapper. The interface is identical to the C libdm counterpart.
48
+func (l DefaultLogger) DMLog(level int, file string, line, dmError int, message string) {
49
+	if int(level) <= l.Level {
50
+		// Forward the log to the correct logrus level, if allowed by dmLogLevel.
51
+		logMsg := fmt.Sprintf("libdevmapper(%d): %s:%d (%d) %s", level, file, line, dmError, message)
52
+		switch level {
53
+		case LogLevelFatal, LogLevelErr:
54
+			logrus.Error(logMsg)
55
+		case LogLevelWarn:
56
+			logrus.Warn(logMsg)
57
+		case LogLevelNotice, LogLevelInfo:
58
+			logrus.Info(logMsg)
59
+		case LogLevelDebug:
60
+			logrus.Debug(logMsg)
61
+		default:
62
+			// Don't drop any "unknown" levels.
63
+			logrus.Info(logMsg)
64
+		}
65
+	}
66
+}
67
+
68
+// registerLogCallback registers our own logging callback function for libdm
69
+// (which is DevmapperLogCallback).
70
+//
71
+// Because libdm only gives us {0,1} error codes we need to parse the logs
72
+// produced by libdm (to set dmSawBusy and so on). Note that by registering a
73
+// callback using DevmapperLogCallback, libdm will no longer output logs to
74
+// stderr so we have to log everything ourselves. None of this handling is
75
+// optional because we depend on log callbacks to parse the logs, and if we
76
+// don't forward the log information we'll be in a lot of trouble when
77
+// debugging things.
78
+func registerLogCallback() {
79
+	LogWithErrnoInit()
80
+}
81
+
82
+func init() {
83
+	// Use the default logger by default. We only allow LogLevelFatal by
84
+	// default, because internally we mask a lot of libdm errors by retrying
85
+	// and similar tricks. Also, libdm is very chatty and we don't want to
86
+	// worry users for no reason.
87
+	dmLogger = DefaultLogger{
88
+		Level: LogLevelFatal,
89
+	}
90
+
91
+	// Register as early as possible so we don't miss anything.
92
+	registerLogCallback()
93
+}
... ...
@@ -4,6 +4,7 @@ package devicemapper
4 4
 
5 5
 /*
6 6
 #cgo LDFLAGS: -L. -ldevmapper
7
+#define _GNU_SOURCE
7 8
 #include <libdevmapper.h>
8 9
 #include <linux/fs.h>   // FIXME: present only for BLKGETSIZE64, maybe we can remove it?
9 10
 
... ...
@@ -12,19 +13,20 @@ extern void DevmapperLogCallback(int level, char *file, int line, int dm_errno_o
12 12
 
13 13
 static void	log_cb(int level, const char *file, int line, int dm_errno_or_class, const char *f, ...)
14 14
 {
15
-  char buffer[256];
16
-  va_list ap;
15
+	char *buffer = NULL;
16
+	va_list ap;
17 17
 
18
-  va_start(ap, f);
19
-  vsnprintf(buffer, 256, f, ap);
20
-  va_end(ap);
18
+	va_start(ap, f);
19
+	vasprintf(&buffer, f, ap);
20
+	va_end(ap);
21 21
 
22
-  DevmapperLogCallback(level, (char *)file, line, dm_errno_or_class, buffer);
22
+	DevmapperLogCallback(level, (char *)file, line, dm_errno_or_class, buffer);
23
+	free(buffer);
23 24
 }
24 25
 
25 26
 static void	log_with_errno_init()
26 27
 {
27
-  dm_log_with_errno_init(log_cb);
28
+	dm_log_with_errno_init(log_cb);
28 29
 }
29 30
 */
30 31
 import "C"
... ...
@@ -56,7 +58,6 @@ const (
56 56
 var (
57 57
 	DmGetLibraryVersion       = dmGetLibraryVersionFct
58 58
 	DmGetNextTarget           = dmGetNextTargetFct
59
-	DmLogInitVerbose          = dmLogInitVerboseFct
60 59
 	DmSetDevDir               = dmSetDevDirFct
61 60
 	DmTaskAddTarget           = dmTaskAddTargetFct
62 61
 	DmTaskCreate              = dmTaskCreateFct
... ...
@@ -226,10 +227,6 @@ func dmCookieSupportedFct() int {
226 226
 	return int(C.dm_cookie_supported())
227 227
 }
228 228
 
229
-func dmLogInitVerboseFct(level int) {
230
-	C.dm_log_init_verbose(C.int(level))
231
-}
232
-
233 229
 func logWithErrnoInitFct() {
234 230
 	C.log_with_errno_init()
235 231
 }