pkg/devicemapper/devmapper_log.go
85357a11
 // +build linux,cgo
24c03b2d
 
e2f8fbfb
 package devicemapper
b440ec01
 
 import "C"
 
586a511c
 import (
cfd39e8d
 	"fmt"
586a511c
 	"strings"
cfd39e8d
 
1009e6a4
 	"github.com/sirupsen/logrus"
586a511c
 )
 
cfd39e8d
 // DevmapperLogger defines methods required to register as a callback for
94cefa21
 // logging events received from devicemapper. Note that devicemapper will send
cfd39e8d
 // *all* logs regardless to callbacks (including debug logs) so it's
 // recommended to not spam the console with the outputs.
 type DevmapperLogger interface {
 	// DMLog is the logging callback containing all of the information from
 	// devicemapper. The interface is identical to the C libdm counterpart.
 	DMLog(level int, file string, line int, dmError int, message string)
 }
 
 // dmLogger is the current logger in use that is being forwarded our messages.
 var dmLogger DevmapperLogger
 
 // LogInit changes the logging callback called after processing libdm logs for
 // error message information. The default logger simply forwards all logs to
 // logrus. Calling LogInit(nil) disables the calling of callbacks.
 func LogInit(logger DevmapperLogger) {
 	dmLogger = logger
 }
 
b440ec01
 // Due to the way cgo works this has to be in a separate file, as devmapper.go has
 // definitions in the cgo block, which is incompatible with using "//export"
 
cfd39e8d
 // DevmapperLogCallback exports the devmapper log callback for cgo. Note that
 // because we are using callbacks, this function will be called for *every* log
 // in libdm (even debug ones because there's no way of setting the verbosity
 // level for an external logging callback).
b440ec01
 //export DevmapperLogCallback
cfd39e8d
 func DevmapperLogCallback(level C.int, file *C.char, line, dmErrnoOrClass C.int, message *C.char) {
586a511c
 	msg := C.GoString(message)
cfd39e8d
 
 	// Track what errno libdm saw, because the library only gives us 0 or 1.
 	if level < LogLevelDebug {
586a511c
 		if strings.Contains(msg, "busy") {
 			dmSawBusy = true
 		}
f26203cf
 
 		if strings.Contains(msg, "File exists") {
 			dmSawExist = true
 		}
20b38f42
 
 		if strings.Contains(msg, "No such device or address") {
 			dmSawEnxio = true
 		}
8451d03d
 		if strings.Contains(msg, "No data available") {
 			dmSawEnoData = true
 		}
586a511c
 	}
 
b440ec01
 	if dmLogger != nil {
0c70eb83
 		dmLogger.DMLog(int(level), C.GoString(file), int(line), int(dmErrnoOrClass), msg)
b440ec01
 	}
 }
cfd39e8d
 
 // DefaultLogger is the default logger used by pkg/devicemapper. It forwards
 // all logs that are of higher or equal priority to the given level to the
 // corresponding logrus level.
 type DefaultLogger struct {
 	// Level corresponds to the highest libdm level that will be forwarded to
 	// logrus. In order to change this, register a new DefaultLogger.
 	Level int
 }
 
 // DMLog is the logging callback containing all of the information from
 // devicemapper. The interface is identical to the C libdm counterpart.
 func (l DefaultLogger) DMLog(level int, file string, line, dmError int, message string) {
2f5f0af3
 	if level <= l.Level {
cfd39e8d
 		// Forward the log to the correct logrus level, if allowed by dmLogLevel.
 		logMsg := fmt.Sprintf("libdevmapper(%d): %s:%d (%d) %s", level, file, line, dmError, message)
 		switch level {
 		case LogLevelFatal, LogLevelErr:
 			logrus.Error(logMsg)
 		case LogLevelWarn:
 			logrus.Warn(logMsg)
 		case LogLevelNotice, LogLevelInfo:
 			logrus.Info(logMsg)
 		case LogLevelDebug:
 			logrus.Debug(logMsg)
 		default:
 			// Don't drop any "unknown" levels.
 			logrus.Info(logMsg)
 		}
 	}
 }
 
 // registerLogCallback registers our own logging callback function for libdm
 // (which is DevmapperLogCallback).
 //
 // Because libdm only gives us {0,1} error codes we need to parse the logs
 // produced by libdm (to set dmSawBusy and so on). Note that by registering a
 // callback using DevmapperLogCallback, libdm will no longer output logs to
 // stderr so we have to log everything ourselves. None of this handling is
 // optional because we depend on log callbacks to parse the logs, and if we
 // don't forward the log information we'll be in a lot of trouble when
 // debugging things.
 func registerLogCallback() {
 	LogWithErrnoInit()
 }
 
 func init() {
 	// Use the default logger by default. We only allow LogLevelFatal by
 	// default, because internally we mask a lot of libdm errors by retrying
 	// and similar tricks. Also, libdm is very chatty and we don't want to
 	// worry users for no reason.
 	dmLogger = DefaultLogger{
 		Level: LogLevelFatal,
 	}
 
 	// Register as early as possible so we don't miss anything.
 	registerLogCallback()
 }