osbase/logtree.LeveledLogger -> go/logging.Leveled

This factors out the common leveled logger interface out of the logtree.
We want to use the same interface outside of logtree/supervisor usage
within the resolver code, which will be exposed to clients.

Change-Id: I299e76d91e8cefddf8f36f1e58432418c4694df2
Reviewed-on: https://review.monogon.dev/c/monogon/+/3411
Reviewed-by: Tim Windelschmidt <tim@monogon.tech>
Tested-by: Jenkins CI
diff --git a/osbase/logtree/BUILD.bazel b/osbase/logtree/BUILD.bazel
index 7c13aeb..60e8743 100644
--- a/osbase/logtree/BUILD.bazel
+++ b/osbase/logtree/BUILD.bazel
@@ -23,6 +23,7 @@
     importpath = "source.monogon.dev/osbase/logtree",
     visibility = ["//visibility:public"],
     deps = [
+        "//go/logging",
         "//osbase/logbuffer",
         "//osbase/logtree/proto",
         "@com_github_mitchellh_go_wordwrap//:go-wordwrap",
@@ -52,6 +53,7 @@
     ],
     embed = [":logtree"],
     deps = [
+        "//go/logging",
         "@com_github_google_go_cmp//cmp",
         "@org_uber_go_zap//:zap",
     ],
diff --git a/osbase/logtree/grpc.go b/osbase/logtree/grpc.go
index 3b2594d..7e17c56 100644
--- a/osbase/logtree/grpc.go
+++ b/osbase/logtree/grpc.go
@@ -1,9 +1,13 @@
 package logtree
 
-import "google.golang.org/grpc/grpclog"
+import (
+	"google.golang.org/grpc/grpclog"
+
+	"source.monogon.dev/go/logging"
+)
 
 // GRPCify turns a LeveledLogger into a go-grpc compatible logger.
-func GRPCify(logger LeveledLogger) grpclog.LoggerV2 {
+func GRPCify(logger logging.Leveled) grpclog.LoggerV2 {
 	lp, ok := logger.(*leveledPublisher)
 	if !ok {
 		// Fail fast, as this is a programming error.
@@ -71,5 +75,5 @@
 }
 
 func (g *leveledGRPCV2) V(l int) bool {
-	return g.lp.V(VerbosityLevel(l)).Enabled()
+	return g.lp.V(logging.VerbosityLevel(l)).Enabled()
 }
diff --git a/osbase/logtree/journal.go b/osbase/logtree/journal.go
index 412c042..d0e8663 100644
--- a/osbase/logtree/journal.go
+++ b/osbase/logtree/journal.go
@@ -21,6 +21,8 @@
 	"sort"
 	"strings"
 	"sync"
+
+	"source.monogon.dev/go/logging"
 )
 
 // DN is the Distinguished Name, a dot-delimited path used to address loggers
@@ -166,7 +168,7 @@
 // filterSeverity returns a filter that accepts log entries at a given severity
 // level or above. See the Severity type for more information about severity
 // levels.
-func filterSeverity(atLeast Severity) filter {
+func filterSeverity(atLeast logging.Severity) filter {
 	return func(e *entry) bool {
 		return e.leveled != nil && e.leveled.severity.AtLeast(atLeast)
 	}
diff --git a/osbase/logtree/journal_test.go b/osbase/logtree/journal_test.go
index e9fc3b4..d295732 100644
--- a/osbase/logtree/journal_test.go
+++ b/osbase/logtree/journal_test.go
@@ -21,13 +21,15 @@
 	"strings"
 	"testing"
 	"time"
+
+	"source.monogon.dev/go/logging"
 )
 
 func testPayload(msg string) *LeveledPayload {
 	return &LeveledPayload{
 		messages:  []string{msg},
 		timestamp: time.Now(),
-		severity:  INFO,
+		severity:  logging.INFO,
 		file:      "main.go",
 		line:      1337,
 	}
diff --git a/osbase/logtree/klog.go b/osbase/logtree/klog.go
index ad7e162..dca5a82 100644
--- a/osbase/logtree/klog.go
+++ b/osbase/logtree/klog.go
@@ -24,6 +24,7 @@
 	"strings"
 	"time"
 
+	"source.monogon.dev/go/logging"
 	"source.monogon.dev/osbase/logbuffer"
 )
 
@@ -47,7 +48,7 @@
 // lines do not necessarily have their year aleays equal to the current year, as
 // the code handles the edge case of parsing a line from the end of a previous
 // year at the beginning of the next).
-func KLogParser(logger LeveledLogger) io.WriteCloser {
+func KLogParser(logger logging.Leveled) io.WriteCloser {
 	p, ok := logger.(*leveledPublisher)
 	if !ok {
 		// Fail fast, as this is a programming error.
@@ -121,16 +122,16 @@
 	lineS := parts[7]
 	message := parts[8]
 
-	var severity Severity
+	var severity logging.Severity
 	switch severityS {
 	case "I":
-		severity = INFO
+		severity = logging.INFO
 	case "W":
-		severity = WARNING
+		severity = logging.WARNING
 	case "E":
-		severity = ERROR
+		severity = logging.ERROR
 	case "F":
-		severity = FATAL
+		severity = logging.FATAL
 	default:
 		return nil
 	}
diff --git a/osbase/logtree/klog_test.go b/osbase/logtree/klog_test.go
index d53df3f..788a7eb 100644
--- a/osbase/logtree/klog_test.go
+++ b/osbase/logtree/klog_test.go
@@ -21,6 +21,8 @@
 	"time"
 
 	"github.com/google/go-cmp/cmp"
+
+	"source.monogon.dev/go/logging"
 )
 
 func TestParse(t *testing.T) {
@@ -40,7 +42,7 @@
 		{now, "E0312 14:20:04.240540    204 shared_informer.go:247] Caches are synced for attach detach", &LeveledPayload{
 			messages:  []string{"Caches are synced for attach detach"},
 			timestamp: time.Date(2021, 03, 12, 14, 20, 4, 240540000, time.UTC),
-			severity:  ERROR,
+			severity:  logging.ERROR,
 			file:      "shared_informer.go",
 			line:      247,
 		}},
@@ -56,7 +58,7 @@
 		{nowNewYear, "I1231 23:59:43.123456    123 fry.go:123] Here's to another lousy millenium!", &LeveledPayload{
 			messages:  []string{"Here's to another lousy millenium!"},
 			timestamp: time.Date(1999, 12, 31, 23, 59, 43, 123456000, time.UTC),
-			severity:  INFO,
+			severity:  logging.INFO,
 			file:      "fry.go",
 			line:      123,
 		}},
@@ -68,7 +70,7 @@
 		{now, "E0312 14:20:04 204 shared_informer.go:247] Caches are synced for attach detach", &LeveledPayload{
 			messages:  []string{"Caches are synced for attach detach"},
 			timestamp: time.Date(2021, 03, 12, 14, 20, 4, 0, time.UTC),
-			severity:  ERROR,
+			severity:  logging.ERROR,
 			file:      "shared_informer.go",
 			line:      247,
 		}},
diff --git a/osbase/logtree/kmsg.go b/osbase/logtree/kmsg.go
index 03bb6ff..11e140a 100644
--- a/osbase/logtree/kmsg.go
+++ b/osbase/logtree/kmsg.go
@@ -14,6 +14,8 @@
 	"time"
 
 	"golang.org/x/sys/unix"
+
+	"source.monogon.dev/go/logging"
 )
 
 const (
@@ -29,7 +31,7 @@
 
 // KmsgPipe pipes logs from the kernel kmsg interface at /dev/kmsg into the
 // given logger.
-func KmsgPipe(ctx context.Context, lt LeveledLogger) error {
+func KmsgPipe(ctx context.Context, lt logging.Leveled) error {
 	publisher, ok := lt.(*leveledPublisher)
 	if !ok {
 		// Fail fast, as this is a programming error.
@@ -119,18 +121,18 @@
 
 	monotonicFromNow := monotonic - monotonicSinceBoot
 
-	var severity Severity
+	var severity logging.Severity
 	switch loglevel {
 	case loglevelEmergency, loglevelAlert:
-		severity = FATAL
+		severity = logging.FATAL
 	case loglevelCritical, loglevelError:
-		severity = ERROR
+		severity = logging.ERROR
 	case loglevelWarning:
-		severity = WARNING
+		severity = logging.WARNING
 	case loglevelNotice, loglevelInfo, loglevelDebug:
-		severity = INFO
+		severity = logging.INFO
 	default:
-		severity = INFO
+		severity = logging.INFO
 	}
 
 	return &LeveledPayload{
diff --git a/osbase/logtree/kmsg_test.go b/osbase/logtree/kmsg_test.go
index e2faf82..24f2acf 100644
--- a/osbase/logtree/kmsg_test.go
+++ b/osbase/logtree/kmsg_test.go
@@ -8,6 +8,8 @@
 	"time"
 
 	"github.com/google/go-cmp/cmp"
+
+	"source.monogon.dev/go/logging"
 )
 
 func TestParseKmsg(t *testing.T) {
@@ -26,13 +28,13 @@
 		{"6,30962,1501094342185,-;test\n", &LeveledPayload{
 			messages:  []string{"test"},
 			timestamp: time.Date(2023, 8, 9, 14, 57, 23, 35675222, time.UTC),
-			severity:  INFO,
+			severity:  logging.INFO,
 		}},
 		// With metadata and different severity
 		{"4,30951,1486884175312,-;nvme nvme2: starting error recovery\n SUBSYSTEM=nvme\n DEVICE=c239:2\n", &LeveledPayload{
 			messages:  []string{"nvme nvme2: starting error recovery"},
 			timestamp: time.Date(2023, 8, 9, 11, 00, 32, 868802222, time.UTC),
-			severity:  WARNING,
+			severity:  logging.WARNING,
 		}},
 	} {
 		got := parseKmsg(now, nowMonotonic, []byte(te.line))
diff --git a/osbase/logtree/leveled.go b/osbase/logtree/leveled.go
index 98699b8..701d8d0 100644
--- a/osbase/logtree/leveled.go
+++ b/osbase/logtree/leveled.go
@@ -19,155 +19,34 @@
 import (
 	"fmt"
 
+	"source.monogon.dev/go/logging"
 	lpb "source.monogon.dev/osbase/logtree/proto"
 )
 
-// LeveledLogger is a generic interface for glog-style logging. There are four
-// hardcoded log severities, in increasing order: INFO, WARNING, ERROR, FATAL.
-// Logging at a certain severity level logs not only to consumers expecting data at
-// that severity level, but also all lower severity levels. For example, an ERROR
-// log will also be passed to consumers looking at INFO or WARNING logs.
-type LeveledLogger interface {
-	// Info logs at the INFO severity. Arguments are handled in the manner of
-	// fmt.Print, a terminating newline is added if missing.
-	Info(args ...interface{})
-	// Infof logs at the INFO severity. Arguments are handled in the manner of
-	// fmt.Printf, a terminating newline is added if missing.
-	Infof(format string, args ...interface{})
-
-	// Warning logs at the WARNING severity. Arguments are handled in the manner of
-	// fmt.Print, a terminating newline is added if missing.
-	Warning(args ...interface{})
-	// Warningf logs at the WARNING severity. Arguments are handled in the manner of
-	// fmt.Printf, a terminating newline is added if missing.
-	Warningf(format string, args ...interface{})
-
-	// Error logs at the ERROR severity. Arguments are handled in the manner of
-	// fmt.Print, a terminating newline is added if missing.
-	Error(args ...interface{})
-	// Errorf logs at the ERROR severity. Arguments are handled in the manner of
-	// fmt.Printf, a terminating newline is added if missing.
-	Errorf(format string, args ...interface{})
-
-	// Fatal logs at the FATAL severity and aborts the current program. Arguments are
-	// handled in the manner of fmt.Print, a terminating newline is added if missing.
-	Fatal(args ...interface{})
-	// Fatalf logs at the FATAL severity and aborts the current program. Arguments are
-	// handled in the manner of fmt.Printf, a terminating newline is added if missing.
-	Fatalf(format string, args ...interface{})
-
-	// V returns a VerboseLeveledLogger at a given verbosity level. These verbosity
-	// levels can be dynamically set and unset on a package-granular level by consumers
-	// of the LeveledLogger logs. The returned value represents whether logging at the
-	// given verbosity level was active at that time, and as such should not be a long-
-	// lived object in programs. This construct is further refered to as 'V-logs'.
-	V(level VerbosityLevel) VerboseLeveledLogger
-
-	// WithAddedStackDepth returns the same LeveledLogger, but adjusted with an
-	// additional 'extra stack depth' which will be used to skip a given number of
-	// stack/call frames when determining the location where the error originated.
-	// For example, WithStackDepth(1) will return a logger that will skip one
-	// stack/call frame. Then, with function foo() calling function helper() which
-	// in turns call l.Infof(), the log line will be emitted with the call site of
-	// helper() within foo(), instead of the default behaviour of logging the
-	// call site of Infof() within helper().
-	//
-	// This is useful for functions which somehow wrap loggers in helper functions,
-	// for example to expose a slightly different API.
-	WithAddedStackDepth(depth int) LeveledLogger
-}
-
-// VerbosityLevel is a verbosity level defined for V-logs. This can be changed
-// programmatically per Go package. When logging at a given VerbosityLevel V, the
-// current level must be equal or higher to V for the logs to be recorded.
-// Conversely, enabling a V-logging at a VerbosityLevel V also enables all logging
-// at lower levels [Int32Min .. (V-1)].
-type VerbosityLevel int32
-
-type VerboseLeveledLogger interface {
-	// Enabled returns if this level was enabled. If not enabled, all logging into this
-	// logger will be discarded immediately. Thus, Enabled() can be used to check the
-	// verbosity level before performing any logging:
-	//    if l.V(3).Enabled() { l.Info("V3 is enabled") }
-	// or, in simple cases, the convenience function .Info can be used:
-	//    l.V(3).Info("V3 is enabled")
-	// The second form is shorter and more convenient, but more expensive, as its
-	// arguments are always evaluated.
-	Enabled() bool
-	// Info is the equivalent of a LeveledLogger's Info call, guarded by whether this
-	// VerboseLeveledLogger is enabled.
-	Info(args ...interface{})
-	// Infof is the equivalent of a LeveledLogger's Infof call, guarded by whether this
-	// VerboseLeveledLogger is enabled.
-	Infof(format string, args ...interface{})
-}
-
-// Severity is one of the severities as described in LeveledLogger.
-type Severity string
-
-const (
-	INFO    Severity = "I"
-	WARNING Severity = "W"
-	ERROR   Severity = "E"
-	FATAL   Severity = "F"
-)
-
-var (
-	// SeverityAtLeast maps a given severity to a list of severities that at that
-	// severity or higher. In other words, SeverityAtLeast[X] returns a list of
-	// severities that might be seen in a log at severity X.
-	SeverityAtLeast = map[Severity][]Severity{
-		INFO:    {INFO, WARNING, ERROR, FATAL},
-		WARNING: {WARNING, ERROR, FATAL},
-		ERROR:   {ERROR, FATAL},
-		FATAL:   {FATAL},
-	}
-)
-
-func (s Severity) AtLeast(other Severity) bool {
-	for _, el := range SeverityAtLeast[other] {
-		if el == s {
-			return true
-		}
-	}
-	return false
-}
-
-// Valid returns whether true if this severity is one of the known levels
-// (INFO, WARNING, ERROR or FATAL), false otherwise.
-func (s Severity) Valid() bool {
-	switch s {
-	case INFO, WARNING, ERROR, FATAL:
-		return true
-	default:
-		return false
-	}
-}
-
-func SeverityFromProto(s lpb.LeveledLogSeverity) (Severity, error) {
+func SeverityFromProto(s lpb.LeveledLogSeverity) (logging.Severity, error) {
 	switch s {
 	case lpb.LeveledLogSeverity_INFO:
-		return INFO, nil
+		return logging.INFO, nil
 	case lpb.LeveledLogSeverity_WARNING:
-		return WARNING, nil
+		return logging.WARNING, nil
 	case lpb.LeveledLogSeverity_ERROR:
-		return ERROR, nil
+		return logging.ERROR, nil
 	case lpb.LeveledLogSeverity_FATAL:
-		return FATAL, nil
+		return logging.FATAL, nil
 	default:
 		return "", fmt.Errorf("unknown severity value %d", s)
 	}
 }
 
-func (s Severity) ToProto() lpb.LeveledLogSeverity {
+func SeverityToProto(s logging.Severity) lpb.LeveledLogSeverity {
 	switch s {
-	case INFO:
+	case logging.INFO:
 		return lpb.LeveledLogSeverity_INFO
-	case WARNING:
+	case logging.WARNING:
 		return lpb.LeveledLogSeverity_WARNING
-	case ERROR:
+	case logging.ERROR:
 		return lpb.LeveledLogSeverity_ERROR
-	case FATAL:
+	case logging.FATAL:
 		return lpb.LeveledLogSeverity_FATAL
 	default:
 		return lpb.LeveledLogSeverity_INVALID
diff --git a/osbase/logtree/leveled_payload.go b/osbase/logtree/leveled_payload.go
index 95b9d5c..b038848 100644
--- a/osbase/logtree/leveled_payload.go
+++ b/osbase/logtree/leveled_payload.go
@@ -24,6 +24,7 @@
 
 	tpb "google.golang.org/protobuf/types/known/timestamppb"
 
+	"source.monogon.dev/go/logging"
 	lpb "source.monogon.dev/osbase/logtree/proto"
 )
 
@@ -38,7 +39,7 @@
 	// timestamp is the time at which this message was emitted.
 	timestamp time.Time
 	// severity is the leveled Severity at which this message was emitted.
-	severity Severity
+	severity logging.Severity
 	// file is the filename of the caller that emitted this message.
 	file string
 	// line is the line number within the file of the caller that emitted this message.
@@ -111,14 +112,14 @@
 func (p *LeveledPayload) Location() string { return fmt.Sprintf("%s:%d", p.file, p.line) }
 
 // Severity returns the Severity with which this entry was logged.
-func (p *LeveledPayload) Severity() Severity { return p.severity }
+func (p *LeveledPayload) Severity() logging.Severity { return p.severity }
 
 // Proto converts a LeveledPayload to protobuf format.
 func (p *LeveledPayload) Proto() *lpb.LogEntry_Leveled {
 	return &lpb.LogEntry_Leveled{
 		Lines:     p.Messages(),
 		Timestamp: tpb.New(p.Timestamp()),
-		Severity:  p.Severity().ToProto(),
+		Severity:  SeverityToProto(p.Severity()),
 		Location:  p.Location(),
 	}
 }
@@ -167,7 +168,7 @@
 	// given, will default to the time of conversion to LeveledPayload.
 	Timestamp time.Time
 	// Log severity. If invalid or unset will default to INFO.
-	Severity Severity
+	Severity logging.Severity
 	// File name of originating code. Defaults to "unknown" if not set.
 	File string
 	// Line in File. Zero indicates the line is not known.
@@ -188,7 +189,7 @@
 		l.timestamp = time.Now()
 	}
 	if !l.severity.Valid() {
-		l.severity = INFO
+		l.severity = logging.INFO
 	}
 	if l.file == "" {
 		l.file = "unknown"
diff --git a/osbase/logtree/logtree.go b/osbase/logtree/logtree.go
index c20681d..b6f8a06 100644
--- a/osbase/logtree/logtree.go
+++ b/osbase/logtree/logtree.go
@@ -21,6 +21,7 @@
 	"strings"
 	"sync"
 
+	"source.monogon.dev/go/logging"
 	"source.monogon.dev/osbase/logbuffer"
 )
 
@@ -52,7 +53,7 @@
 	tree *LogTree
 	// verbosity is the current verbosity level of this DN/node, affecting .V(n)
 	// LeveledLogger calls
-	verbosity     VerbosityLevel
+	verbosity     logging.VerbosityLevel
 	rawLineBuffer *logbuffer.LineBuffer
 
 	// mu guards children.
diff --git a/osbase/logtree/logtree_access.go b/osbase/logtree/logtree_access.go
index b601ea4..30ceccf 100644
--- a/osbase/logtree/logtree_access.go
+++ b/osbase/logtree/logtree_access.go
@@ -19,6 +19,8 @@
 import (
 	"errors"
 	"sync/atomic"
+
+	"source.monogon.dev/go/logging"
 )
 
 // LogReadOption describes options for the LogTree.Read call.
@@ -28,7 +30,7 @@
 	withBacklog                int
 	onlyLeveled                bool
 	onlyRaw                    bool
-	leveledWithMinimumSeverity Severity
+	leveledWithMinimumSeverity logging.Severity
 }
 
 // WithChildren makes Read return/stream data for both a given DN and all its
@@ -54,7 +56,7 @@
 // LeveledWithMinimumSeverity makes Read return only log entries that are at least
 // at a given Severity. If only leveled entries are needed, OnlyLeveled must be
 // used. This is a no-op when OnlyRaw is used.
-func LeveledWithMinimumSeverity(s Severity) LogReadOption {
+func LeveledWithMinimumSeverity(s logging.Severity) LogReadOption {
 	return LogReadOption{leveledWithMinimumSeverity: s}
 }
 
@@ -111,7 +113,7 @@
 	var backlog int
 	var stream bool
 	var recursive bool
-	var leveledSeverity Severity
+	var leveledSeverity logging.Severity
 	var onlyRaw, onlyLeveled bool
 
 	for _, opt := range opts {
diff --git a/osbase/logtree/logtree_publisher.go b/osbase/logtree/logtree_publisher.go
index 6c4120a..2df7037 100644
--- a/osbase/logtree/logtree_publisher.go
+++ b/osbase/logtree/logtree_publisher.go
@@ -23,6 +23,7 @@
 	"strings"
 	"time"
 
+	"source.monogon.dev/go/logging"
 	"source.monogon.dev/osbase/logbuffer"
 )
 
@@ -33,7 +34,7 @@
 
 // LeveledFor returns a LeveledLogger publishing interface for a given DN. An error
 // may be returned if the DN is malformed.
-func (l *LogTree) LeveledFor(dn DN) (LeveledLogger, error) {
+func (l *LogTree) LeveledFor(dn DN) (logging.Leveled, error) {
 	node, err := l.nodeByDN(dn)
 	if err != nil {
 		return nil, err
@@ -54,7 +55,7 @@
 
 // MustLeveledFor returns a LeveledLogger publishing interface for a given DN, or
 // panics if the given DN is invalid.
-func (l *LogTree) MustLeveledFor(dn DN) LeveledLogger {
+func (l *LogTree) MustLeveledFor(dn DN) logging.Leveled {
 	leveled, err := l.LeveledFor(dn)
 	if err != nil {
 		panic(fmt.Errorf("LeveledFor returned: %w", err))
@@ -72,7 +73,7 @@
 
 // SetVerbosity sets the verbosity for a given DN (non-recursively, ie. for that DN
 // only, not its children).
-func (l *LogTree) SetVerbosity(dn DN, level VerbosityLevel) error {
+func (l *LogTree) SetVerbosity(dn DN, level logging.VerbosityLevel) error {
 	node, err := l.nodeByDN(dn)
 	if err != nil {
 		return err
@@ -97,7 +98,7 @@
 // LeveledLogger. This should only be used by systems which translate external
 // data sources into leveled logging - see ExternelLeveledPayload for more
 // information.
-func LogExternalLeveled(l LeveledLogger, e *ExternalLeveledPayload) error {
+func LogExternalLeveled(l logging.Leveled, e *ExternalLeveledPayload) error {
 	publisher, ok := l.(*leveledPublisher)
 	if !ok {
 		return fmt.Errorf("the given LeveledLogger is not a *leveledPublisher")
@@ -115,7 +116,7 @@
 // log builds a LeveledPayload and entry for a given message, including all related
 // metadata. It will create a new entry append it to the journal, and notify all
 // pertinent subscribers.
-func (l *leveledPublisher) logLeveled(depth int, severity Severity, msg string) {
+func (l *leveledPublisher) logLeveled(depth int, severity logging.Severity, msg string) {
 	_, file, line, ok := runtime.Caller(2 + depth)
 	if !ok {
 		file = "???"
@@ -147,53 +148,53 @@
 
 // Info implements the LeveledLogger interface.
 func (l *leveledPublisher) Info(args ...interface{}) {
-	l.logLeveled(l.depth, INFO, fmt.Sprint(args...))
+	l.logLeveled(l.depth, logging.INFO, fmt.Sprint(args...))
 }
 
 // Infof implements the LeveledLogger interface.
 func (l *leveledPublisher) Infof(format string, args ...interface{}) {
-	l.logLeveled(l.depth, INFO, fmt.Sprintf(format, args...))
+	l.logLeveled(l.depth, logging.INFO, fmt.Sprintf(format, args...))
 }
 
 // Warning implements the LeveledLogger interface.
 func (l *leveledPublisher) Warning(args ...interface{}) {
-	l.logLeveled(l.depth, WARNING, fmt.Sprint(args...))
+	l.logLeveled(l.depth, logging.WARNING, fmt.Sprint(args...))
 }
 
 // Warningf implements the LeveledLogger interface.
 func (l *leveledPublisher) Warningf(format string, args ...interface{}) {
-	l.logLeveled(l.depth, WARNING, fmt.Sprintf(format, args...))
+	l.logLeveled(l.depth, logging.WARNING, fmt.Sprintf(format, args...))
 }
 
 // Error implements the LeveledLogger interface.
 func (l *leveledPublisher) Error(args ...interface{}) {
-	l.logLeveled(l.depth, ERROR, fmt.Sprint(args...))
+	l.logLeveled(l.depth, logging.ERROR, fmt.Sprint(args...))
 }
 
 // Errorf implements the LeveledLogger interface.
 func (l *leveledPublisher) Errorf(format string, args ...interface{}) {
-	l.logLeveled(l.depth, ERROR, fmt.Sprintf(format, args...))
+	l.logLeveled(l.depth, logging.ERROR, fmt.Sprintf(format, args...))
 }
 
 // Fatal implements the LeveledLogger interface.
 func (l *leveledPublisher) Fatal(args ...interface{}) {
-	l.logLeveled(l.depth, FATAL, fmt.Sprint(args...))
+	l.logLeveled(l.depth, logging.FATAL, fmt.Sprint(args...))
 }
 
 // Fatalf implements the LeveledLogger interface.
 func (l *leveledPublisher) Fatalf(format string, args ...interface{}) {
-	l.logLeveled(l.depth, FATAL, fmt.Sprintf(format, args...))
+	l.logLeveled(l.depth, logging.FATAL, fmt.Sprintf(format, args...))
 }
 
 // WithAddedStackDepth impleemnts the LeveledLogger interface.
-func (l *leveledPublisher) WithAddedStackDepth(depth int) LeveledLogger {
+func (l *leveledPublisher) WithAddedStackDepth(depth int) logging.Leveled {
 	l2 := *l
 	l2.depth += depth
 	return &l2
 }
 
 // V implements the LeveledLogger interface.
-func (l *leveledPublisher) V(v VerbosityLevel) VerboseLeveledLogger {
+func (l *leveledPublisher) V(v logging.VerbosityLevel) logging.VerboseLeveled {
 	return &verbose{
 		publisher: l,
 		enabled:   l.node.verbosity >= v,
@@ -218,12 +219,12 @@
 	if !v.enabled {
 		return
 	}
-	v.publisher.logLeveled(v.publisher.depth, INFO, fmt.Sprint(args...))
+	v.publisher.logLeveled(v.publisher.depth, logging.INFO, fmt.Sprint(args...))
 }
 
 func (v *verbose) Infof(format string, args ...interface{}) {
 	if !v.enabled {
 		return
 	}
-	v.publisher.logLeveled(v.publisher.depth, INFO, fmt.Sprintf(format, args...))
+	v.publisher.logLeveled(v.publisher.depth, logging.INFO, fmt.Sprintf(format, args...))
 }
diff --git a/osbase/logtree/logtree_test.go b/osbase/logtree/logtree_test.go
index 54eabb7..8ddd3d0 100644
--- a/osbase/logtree/logtree_test.go
+++ b/osbase/logtree/logtree_test.go
@@ -21,6 +21,8 @@
 	"strings"
 	"testing"
 	"time"
+
+	"source.monogon.dev/go/logging"
 )
 
 func expect(tree *LogTree, t *testing.T, dn DN, entries ...string) string {
@@ -227,13 +229,13 @@
 
 	for _, te := range []struct {
 		ix       int
-		severity Severity
+		severity logging.Severity
 		message  string
 	}{
-		{0, ERROR, "i am an error"},
-		{1, WARNING, "i am a warning"},
-		{2, INFO, "i am informative"},
-		{3, INFO, "i am a zero-level debug"},
+		{0, logging.ERROR, "i am an error"},
+		{1, logging.WARNING, "i am a warning"},
+		{2, logging.INFO, "i am informative"},
+		{3, logging.INFO, "i am a zero-level debug"},
 	} {
 		p := reader.Backlog[te.ix]
 		if want, got := te.severity, p.Leveled.Severity(); want != got {
@@ -255,7 +257,7 @@
 	tree.MustLeveledFor("main").Info("i am informative")
 	tree.MustLeveledFor("main").V(0).Info("i am a zero-level debug")
 
-	reader, err := tree.Read("main", WithBacklog(BacklogAllAvailable), LeveledWithMinimumSeverity(WARNING))
+	reader, err := tree.Read("main", WithBacklog(BacklogAllAvailable), LeveledWithMinimumSeverity(logging.WARNING))
 	if err != nil {
 		t.Fatalf("Read: %v", err)
 	}
@@ -311,7 +313,7 @@
 			&LogEntry{
 				Leveled: &LeveledPayload{
 					messages: []string{"Hello there!"},
-					severity: WARNING,
+					severity: logging.WARNING,
 				},
 				DN: "root.role.kubernetes.run.kubernetes.apiserver",
 			},
@@ -322,7 +324,7 @@
 			&LogEntry{
 				Leveled: &LeveledPayload{
 					messages: []string{"Hello there!", "I am multiline."},
-					severity: WARNING,
+					severity: logging.WARNING,
 				},
 				DN: "root.role.kubernetes.run.kubernetes.apiserver",
 			},
@@ -336,7 +338,7 @@
 			&LogEntry{
 				Leveled: &LeveledPayload{
 					messages: []string{"Hello there! I am a very long string, and I will get wrapped to 120 columns because that's just how life is for long strings."},
-					severity: WARNING,
+					severity: logging.WARNING,
 				},
 				DN: "root.role.kubernetes.run.kubernetes.apiserver",
 			},
@@ -350,7 +352,7 @@
 			&LogEntry{
 				Leveled: &LeveledPayload{
 					messages: []string{"Hello there!"},
-					severity: WARNING,
+					severity: logging.WARNING,
 				},
 				DN: "root.role.kubernetes.run.kubernetes.apiserver",
 			},
@@ -363,7 +365,7 @@
 			&LogEntry{
 				Leveled: &LeveledPayload{
 					messages: []string{"Hello there!"},
-					severity: WARNING,
+					severity: logging.WARNING,
 				},
 				DN: "root.role.kubernetes.run.kubernetes.apiserver",
 			},
diff --git a/osbase/logtree/unraw/BUILD.bazel b/osbase/logtree/unraw/BUILD.bazel
index 3ae4da1..9364a30 100644
--- a/osbase/logtree/unraw/BUILD.bazel
+++ b/osbase/logtree/unraw/BUILD.bazel
@@ -6,6 +6,7 @@
     importpath = "source.monogon.dev/osbase/logtree/unraw",
     visibility = ["//visibility:public"],
     deps = [
+        "//go/logging",
         "//osbase/logbuffer",
         "//osbase/logtree",
         "//osbase/supervisor",
diff --git a/osbase/logtree/unraw/unraw.go b/osbase/logtree/unraw/unraw.go
index a1f2624..88ab9d1 100644
--- a/osbase/logtree/unraw/unraw.go
+++ b/osbase/logtree/unraw/unraw.go
@@ -24,6 +24,7 @@
 	"syscall"
 	"time"
 
+	"source.monogon.dev/go/logging"
 	"source.monogon.dev/osbase/logbuffer"
 	"source.monogon.dev/osbase/logtree"
 	"source.monogon.dev/osbase/supervisor"
@@ -51,7 +52,7 @@
 	MaximumLineLength int
 	// LeveledLogger is the logtree leveled logger into which events from the
 	// Parser will be sent.
-	LeveledLogger logtree.LeveledLogger
+	LeveledLogger logging.Leveled
 
 	// mu guards lb.
 	mu sync.Mutex
diff --git a/osbase/logtree/zap.go b/osbase/logtree/zap.go
index f3ae6e3..82e6dda 100644
--- a/osbase/logtree/zap.go
+++ b/osbase/logtree/zap.go
@@ -10,13 +10,14 @@
 	"go.uber.org/zap"
 	"go.uber.org/zap/zapcore"
 
+	"source.monogon.dev/go/logging"
 	"source.monogon.dev/osbase/logbuffer"
 )
 
 // Zapify turns a LeveledLogger into a zap.Logger which pipes its output into the
 // LeveledLogger. The message, severity and caller are carried over. Extra fields
 // are appended as JSON to the end of the log line.
-func Zapify(logger LeveledLogger, minimumLevel zapcore.Level) *zap.Logger {
+func Zapify(logger logging.Leveled, minimumLevel zapcore.Level) *zap.Logger {
 	p, ok := logger.(*leveledPublisher)
 	if !ok {
 		// Fail fast, as this is a programming error.
@@ -71,7 +72,7 @@
 
 type zapEntry struct {
 	message  string
-	severity Severity
+	severity logging.Severity
 	time     time.Time
 	file     string
 	line     int
@@ -109,14 +110,14 @@
 	callerLineS := callerParts[1]
 	callerLine, _ := strconv.Atoi(callerLineS)
 
-	var severity Severity
+	var severity logging.Severity
 	switch level {
 	case "warn":
-		severity = WARNING
+		severity = logging.WARNING
 	case "error", "dpanic", "panic", "fatal":
-		severity = ERROR
+		severity = logging.ERROR
 	default:
-		severity = INFO
+		severity = logging.INFO
 	}
 
 	secs := int64(t)
diff --git a/osbase/logtree/zap_test.go b/osbase/logtree/zap_test.go
index 3917cd8..b03b8cf 100644
--- a/osbase/logtree/zap_test.go
+++ b/osbase/logtree/zap_test.go
@@ -4,6 +4,8 @@
 	"testing"
 
 	"go.uber.org/zap"
+
+	"source.monogon.dev/go/logging"
 )
 
 func TestZapify(t *testing.T) {
@@ -25,11 +27,11 @@
 	} else {
 		for i, te := range []struct {
 			msg string
-			sev Severity
+			sev logging.Severity
 		}{
-			{`foo {"intp":42,"strp":"strv"}`, INFO},
-			{`foo! {"intp":1337,"strp":"strv"}`, WARNING},
-			{`foo!!`, ERROR},
+			{`foo {"intp":42,"strp":"strv"}`, logging.INFO},
+			{`foo! {"intp":1337,"strp":"strv"}`, logging.WARNING},
+			{`foo!!`, logging.ERROR},
 		} {
 			if want, got := te.msg, res.Backlog[i].Leveled.messages[0]; want != got {
 				t.Errorf("Line %d: wanted message %q, got %q", i, want, got)
diff --git a/osbase/supervisor/BUILD.bazel b/osbase/supervisor/BUILD.bazel
index b6b4861..9dfda5a 100644
--- a/osbase/supervisor/BUILD.bazel
+++ b/osbase/supervisor/BUILD.bazel
@@ -15,6 +15,7 @@
     # TODO(#189): move supervisor to //go
     visibility = ["//visibility:public"],
     deps = [
+        "//go/logging",
         "//osbase/logtree",
         "@com_github_cenkalti_backoff_v4//:backoff",
         "@com_github_prometheus_client_golang//prometheus",
diff --git a/osbase/supervisor/supervisor.go b/osbase/supervisor/supervisor.go
index ff87a25..5570945 100644
--- a/osbase/supervisor/supervisor.go
+++ b/osbase/supervisor/supervisor.go
@@ -27,6 +27,7 @@
 	"io"
 	"sync"
 
+	"source.monogon.dev/go/logging"
 	"source.monogon.dev/osbase/logtree"
 )
 
@@ -94,7 +95,7 @@
 	// logtree is the main logtree exposed to runnables and used internally.
 	logtree *logtree.LogTree
 	// ilogger is the internal logger logging to "supervisor" in the logtree.
-	ilogger logtree.LeveledLogger
+	ilogger logging.Leveled
 
 	// pReq is an interface channel to the lifecycle processor of the
 	// supervisor.
@@ -160,7 +161,7 @@
 	return sup
 }
 
-func Logger(ctx context.Context) logtree.LeveledLogger {
+func Logger(ctx context.Context) logging.Leveled {
 	node, unlock := fromContext(ctx)
 	defer unlock()
 	return node.sup.logtree.MustLeveledFor(logtree.DN(node.dn()))
@@ -182,7 +183,7 @@
 // sub-logger with a given name, that name also becomes unavailable for use as
 // a child runnable (no runnable and sub-logger may ever log into the same
 // logtree DN).
-func SubLogger(ctx context.Context, name string) (logtree.LeveledLogger, error) {
+func SubLogger(ctx context.Context, name string) (logging.Leveled, error) {
 	node, unlock := fromContext(ctx)
 	defer unlock()
 
@@ -201,7 +202,7 @@
 // MustSubLogger is a wrapper around SubLogger which panics on error. Errors
 // should only happen due to invalid names, so as long as the given name is
 // compile-time constant and valid, this function is safe to use.
-func MustSubLogger(ctx context.Context, name string) logtree.LeveledLogger {
+func MustSubLogger(ctx context.Context, name string) logging.Leveled {
 	l, err := SubLogger(ctx, name)
 	if err != nil {
 		panic(err)
diff --git a/osbase/tpm/BUILD.bazel b/osbase/tpm/BUILD.bazel
index d75c9fb..6f33dca 100644
--- a/osbase/tpm/BUILD.bazel
+++ b/osbase/tpm/BUILD.bazel
@@ -9,7 +9,7 @@
     importpath = "source.monogon.dev/osbase/tpm",
     visibility = ["//visibility:public"],
     deps = [
-        "//osbase/logtree",
+        "//go/logging",
         "//osbase/sysfs",
         "//osbase/tpm/proto",
         "@com_github_google_go_tpm//tpm2",
diff --git a/osbase/tpm/tpm.go b/osbase/tpm/tpm.go
index b77c36d..f664c75 100644
--- a/osbase/tpm/tpm.go
+++ b/osbase/tpm/tpm.go
@@ -41,7 +41,7 @@
 
 	tpmpb "source.monogon.dev/osbase/tpm/proto"
 
-	"source.monogon.dev/osbase/logtree"
+	"source.monogon.dev/go/logging"
 	"source.monogon.dev/osbase/sysfs"
 )
 
@@ -126,7 +126,7 @@
 
 // TPM represents a high-level interface to a connected TPM 2.0
 type TPM struct {
-	logger logtree.LeveledLogger
+	logger logging.Leveled
 	device io.ReadWriteCloser
 
 	// We keep the AK loaded since it's used fairly often and deriving it is
@@ -137,7 +137,7 @@
 
 // Initialize finds and opens the TPM (if any). If there is no TPM available it
 // returns ErrNotExists
-func Initialize(logger logtree.LeveledLogger) error {
+func Initialize(logger logging.Leveled) error {
 	lock.Lock()
 	defer lock.Unlock()
 	tpmDir, err := os.Open("/sys/class/tpm")