treewide: introduce osbase package and move things around

All except localregistry moved from metropolis/pkg to osbase,
localregistry moved to metropolis/test as its only used there anyway.

Change-Id: If1a4bf377364bef0ac23169e1b90379c71b06d72
Reviewed-on: https://review.monogon.dev/c/monogon/+/3079
Tested-by: Jenkins CI
Reviewed-by: Serge Bazanski <serge@monogon.tech>
diff --git a/osbase/logtree/logtree_publisher.go b/osbase/logtree/logtree_publisher.go
new file mode 100644
index 0000000..6c4120a
--- /dev/null
+++ b/osbase/logtree/logtree_publisher.go
@@ -0,0 +1,229 @@
+// Copyright 2020 The Monogon Project Authors.
+//
+// SPDX-License-Identifier: Apache-2.0
+//
+// 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 logtree
+
+import (
+	"fmt"
+	"io"
+	"runtime"
+	"strings"
+	"time"
+
+	"source.monogon.dev/osbase/logbuffer"
+)
+
+type leveledPublisher struct {
+	node  *node
+	depth int
+}
+
+// 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) {
+	node, err := l.nodeByDN(dn)
+	if err != nil {
+		return nil, err
+	}
+	return &leveledPublisher{
+		node:  node,
+		depth: 0,
+	}, nil
+}
+
+func (l *LogTree) RawFor(dn DN) (io.Writer, error) {
+	node, err := l.nodeByDN(dn)
+	if err != nil {
+		return nil, fmt.Errorf("could not retrieve raw logger: %w", err)
+	}
+	return node.rawLineBuffer, nil
+}
+
+// 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 {
+	leveled, err := l.LeveledFor(dn)
+	if err != nil {
+		panic(fmt.Errorf("LeveledFor returned: %w", err))
+	}
+	return leveled
+}
+
+func (l *LogTree) MustRawFor(dn DN) io.Writer {
+	raw, err := l.RawFor(dn)
+	if err != nil {
+		panic(fmt.Errorf("RawFor returned: %w", err))
+	}
+	return raw
+}
+
+// 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 {
+	node, err := l.nodeByDN(dn)
+	if err != nil {
+		return err
+	}
+	node.verbosity = level
+	return nil
+}
+
+// logRaw is called by this node's LineBuffer any time a raw log line is completed.
+// It will create a new entry, append it to the journal, and notify all pertinent
+// subscribers.
+func (n *node) logRaw(line *logbuffer.Line) {
+	e := &entry{
+		origin: n.dn,
+		raw:    line,
+	}
+	n.tree.journal.append(e)
+	n.tree.journal.notify(e)
+}
+
+// LogExternalLeveled injects a ExternalLeveledPayload into a given
+// 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 {
+	publisher, ok := l.(*leveledPublisher)
+	if !ok {
+		return fmt.Errorf("the given LeveledLogger is not a *leveledPublisher")
+	}
+	p := e.sanitize()
+	entry := &entry{
+		origin:  publisher.node.dn,
+		leveled: p,
+	}
+	publisher.node.tree.journal.append(entry)
+	publisher.node.tree.journal.notify(entry)
+	return nil
+}
+
+// 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) {
+	_, file, line, ok := runtime.Caller(2 + depth)
+	if !ok {
+		file = "???"
+		line = 1
+	} else {
+		slash := strings.LastIndex(file, "/")
+		if slash >= 0 {
+			file = file[slash+1:]
+		}
+	}
+
+	// Remove leading/trailing newlines and split.
+	messages := strings.Split(strings.Trim(msg, "\n"), "\n")
+
+	p := &LeveledPayload{
+		timestamp: time.Now(),
+		severity:  severity,
+		messages:  messages,
+		file:      file,
+		line:      line,
+	}
+	e := &entry{
+		origin:  l.node.dn,
+		leveled: p,
+	}
+	l.node.tree.journal.append(e)
+	l.node.tree.journal.notify(e)
+}
+
+// Info implements the LeveledLogger interface.
+func (l *leveledPublisher) Info(args ...interface{}) {
+	l.logLeveled(l.depth, 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...))
+}
+
+// Warning implements the LeveledLogger interface.
+func (l *leveledPublisher) Warning(args ...interface{}) {
+	l.logLeveled(l.depth, 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...))
+}
+
+// Error implements the LeveledLogger interface.
+func (l *leveledPublisher) Error(args ...interface{}) {
+	l.logLeveled(l.depth, 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...))
+}
+
+// Fatal implements the LeveledLogger interface.
+func (l *leveledPublisher) Fatal(args ...interface{}) {
+	l.logLeveled(l.depth, 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...))
+}
+
+// WithAddedStackDepth impleemnts the LeveledLogger interface.
+func (l *leveledPublisher) WithAddedStackDepth(depth int) LeveledLogger {
+	l2 := *l
+	l2.depth += depth
+	return &l2
+}
+
+// V implements the LeveledLogger interface.
+func (l *leveledPublisher) V(v VerbosityLevel) VerboseLeveledLogger {
+	return &verbose{
+		publisher: l,
+		enabled:   l.node.verbosity >= v,
+	}
+}
+
+// verbose implements the VerboseLeveledLogger interface. It is a thin wrapper
+// around node, with an 'enabled' bool. This means that V(n)-returned
+// VerboseLeveledLoggers must be short lived, as a changed in verbosity will not
+// affect all already existing VerboseLeveledLoggers.
+type verbose struct {
+	publisher *leveledPublisher
+	node      *node
+	enabled   bool
+}
+
+func (v *verbose) Enabled() bool {
+	return v.enabled
+}
+
+func (v *verbose) Info(args ...interface{}) {
+	if !v.enabled {
+		return
+	}
+	v.publisher.logLeveled(v.publisher.depth, 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...))
+}