metropolis/pkg/logtree: move logtree protobuf definition to logtree pkg

Before this change we had the LogEntry message inside the metropolis
common proto file. This splits it out into the logtree package to make
it standalone in a future change.

Change-Id: Idb26a829d6174efa946a6c4ce0f1b984cb2f18a2
Reviewed-on: https://review.monogon.dev/c/monogon/+/3080
Tested-by: Jenkins CI
Reviewed-by: Serge Bazanski <serge@monogon.tech>
diff --git a/metropolis/cli/metroctl/BUILD.bazel b/metropolis/cli/metroctl/BUILD.bazel
index 9eb0aa4..37f4b56 100644
--- a/metropolis/cli/metroctl/BUILD.bazel
+++ b/metropolis/cli/metroctl/BUILD.bazel
@@ -49,6 +49,7 @@
         "//metropolis/pkg/blkio",
         "//metropolis/pkg/fat32",
         "//metropolis/pkg/logtree",
+        "//metropolis/pkg/logtree/proto",
         "//metropolis/proto/api",
         "//metropolis/proto/common",
         "//version",
diff --git a/metropolis/cli/metroctl/cmd_node_logs.go b/metropolis/cli/metroctl/cmd_node_logs.go
index 36ec3de..77dd8c2 100644
--- a/metropolis/cli/metroctl/cmd_node_logs.go
+++ b/metropolis/cli/metroctl/cmd_node_logs.go
@@ -9,8 +9,8 @@
 
 	"source.monogon.dev/metropolis/cli/metroctl/core"
 	"source.monogon.dev/metropolis/pkg/logtree"
+	lpb "source.monogon.dev/metropolis/pkg/logtree/proto"
 	"source.monogon.dev/metropolis/proto/api"
-
 	cpb "source.monogon.dev/metropolis/proto/common"
 )
 
@@ -141,7 +141,7 @@
 	},
 }
 
-func printEntry(e *cpb.LogEntry) {
+func printEntry(e *lpb.LogEntry) {
 	entry, err := logtree.LogEntryFromProto(e)
 	if err != nil {
 		fmt.Printf("invalid stream entry: %v\n", err)
diff --git a/metropolis/node/core/mgmt/BUILD.bazel b/metropolis/node/core/mgmt/BUILD.bazel
index a274013..ecec6d1 100644
--- a/metropolis/node/core/mgmt/BUILD.bazel
+++ b/metropolis/node/core/mgmt/BUILD.bazel
@@ -15,6 +15,7 @@
         "//metropolis/node/core/rpc",
         "//metropolis/node/core/update",
         "//metropolis/pkg/logtree",
+        "//metropolis/pkg/logtree/proto",
         "//metropolis/pkg/supervisor",
         "//metropolis/proto/api",
         "//metropolis/proto/common",
@@ -32,6 +33,7 @@
     embed = [":mgmt"],
     deps = [
         "//metropolis/pkg/logtree",
+        "//metropolis/pkg/logtree/proto",
         "//metropolis/proto/api",
         "//metropolis/proto/common",
         "@com_github_google_go_cmp//cmp",
diff --git a/metropolis/node/core/mgmt/svc_logs.go b/metropolis/node/core/mgmt/svc_logs.go
index 6566cee..1a884b3 100644
--- a/metropolis/node/core/mgmt/svc_logs.go
+++ b/metropolis/node/core/mgmt/svc_logs.go
@@ -8,6 +8,7 @@
 	"google.golang.org/grpc/status"
 
 	"source.monogon.dev/metropolis/pkg/logtree"
+	lpb "source.monogon.dev/metropolis/pkg/logtree/proto"
 	"source.monogon.dev/metropolis/proto/api"
 	cpb "source.monogon.dev/metropolis/proto/common"
 )
@@ -24,17 +25,17 @@
 
 // sanitizedEntries returns a deep copy of the given log entries, but replaces
 // all invalid UTF-8 characters with "<INVALID>".
-func sanitizedEntries(entries []*cpb.LogEntry) []*cpb.LogEntry {
-	res := make([]*cpb.LogEntry, len(entries))
+func sanitizedEntries(entries []*lpb.LogEntry) []*lpb.LogEntry {
+	res := make([]*lpb.LogEntry, len(entries))
 	for i, entry := range entries {
-		res[i] = &cpb.LogEntry{
+		res[i] = &lpb.LogEntry{
 			Dn:   entry.Dn,
 			Kind: nil,
 		}
 		switch k := entry.Kind.(type) {
-		case *cpb.LogEntry_Leveled_:
-			leveled := &cpb.LogEntry_Leveled_{
-				Leveled: &cpb.LogEntry_Leveled{
+		case *lpb.LogEntry_Leveled_:
+			leveled := &lpb.LogEntry_Leveled_{
+				Leveled: &lpb.LogEntry_Leveled{
 					Lines:     make([]string, len(k.Leveled.Lines)),
 					Timestamp: k.Leveled.Timestamp,
 					Severity:  k.Leveled.Severity,
@@ -46,9 +47,9 @@
 			}
 			res[i].Kind = leveled
 
-		case *cpb.LogEntry_Raw_:
-			res[i].Kind = &cpb.LogEntry_Raw_{
-				Raw: &cpb.LogEntry_Raw{
+		case *lpb.LogEntry_Raw_:
+			res[i].Kind = &lpb.LogEntry_Raw_{
+				Raw: &lpb.LogEntry_Raw{
 					Data:           strings.ToValidUTF8(k.Raw.Data, "<INVALID>"),
 					OriginalLength: k.Raw.OriginalLength,
 				},
@@ -141,7 +142,7 @@
 	maxChunkSize := 2000
 
 	// Serve all backlog entries in chunks.
-	chunk := make([]*cpb.LogEntry, 0, maxChunkSize)
+	chunk := make([]*lpb.LogEntry, 0, maxChunkSize)
 	for _, entry := range reader.Backlog {
 		p := entry.Proto()
 		if p == nil {
@@ -157,7 +158,7 @@
 			if err != nil {
 				return err
 			}
-			chunk = make([]*cpb.LogEntry, 0, maxChunkSize)
+			chunk = make([]*lpb.LogEntry, 0, maxChunkSize)
 		}
 	}
 
@@ -188,7 +189,7 @@
 			continue
 		}
 		err := srv.Send(&api.GetLogsResponse{
-			StreamEntries: []*cpb.LogEntry{p},
+			StreamEntries: []*lpb.LogEntry{p},
 		})
 		if err != nil {
 			return err
diff --git a/metropolis/node/core/mgmt/svc_logs_test.go b/metropolis/node/core/mgmt/svc_logs_test.go
index fea8068..162de57 100644
--- a/metropolis/node/core/mgmt/svc_logs_test.go
+++ b/metropolis/node/core/mgmt/svc_logs_test.go
@@ -17,6 +17,7 @@
 	"google.golang.org/protobuf/testing/protocmp"
 
 	"source.monogon.dev/metropolis/pkg/logtree"
+	lpb "source.monogon.dev/metropolis/pkg/logtree/proto"
 	"source.monogon.dev/metropolis/proto/api"
 	cpb "source.monogon.dev/metropolis/proto/common"
 )
@@ -50,19 +51,19 @@
 	return s, cl
 }
 
-func cleanLogEntry(e *cpb.LogEntry) {
+func cleanLogEntry(e *lpb.LogEntry) {
 	// Filter out bits that change too much to test them.
 	switch k := e.Kind.(type) {
-	case *cpb.LogEntry_Leveled_:
+	case *lpb.LogEntry_Leveled_:
 		k.Leveled.Location = ""
 		k.Leveled.Timestamp = nil
 	}
 }
 
-func mkRawEntry(dn string, line string) *cpb.LogEntry {
-	return &cpb.LogEntry{
-		Dn: dn, Kind: &cpb.LogEntry_Raw_{
-			Raw: &cpb.LogEntry_Raw{
+func mkRawEntry(dn string, line string) *lpb.LogEntry {
+	return &lpb.LogEntry{
+		Dn: dn, Kind: &lpb.LogEntry_Raw_{
+			Raw: &lpb.LogEntry_Raw{
 				Data:           line,
 				OriginalLength: int64(len(line)),
 			},
@@ -70,19 +71,19 @@
 	}
 }
 
-func mkLeveledEntry(dn string, severity string, lines string) *cpb.LogEntry {
-	var sev cpb.LeveledLogSeverity
+func mkLeveledEntry(dn string, severity string, lines string) *lpb.LogEntry {
+	var sev lpb.LeveledLogSeverity
 	switch severity {
 	case "i":
-		sev = cpb.LeveledLogSeverity_INFO
+		sev = lpb.LeveledLogSeverity_INFO
 	case "w":
-		sev = cpb.LeveledLogSeverity_WARNING
+		sev = lpb.LeveledLogSeverity_WARNING
 	case "e":
-		sev = cpb.LeveledLogSeverity_ERROR
+		sev = lpb.LeveledLogSeverity_ERROR
 	}
-	return &cpb.LogEntry{
-		Dn: dn, Kind: &cpb.LogEntry_Leveled_{
-			Leveled: &cpb.LogEntry_Leveled{
+	return &lpb.LogEntry{
+		Dn: dn, Kind: &lpb.LogEntry_Leveled_{
+			Leveled: &lpb.LogEntry_Leveled{
 				Lines:    strings.Split(lines, "\n"),
 				Severity: sev,
 			},
@@ -90,7 +91,7 @@
 	}
 }
 
-func drainLogs(t *testing.T, srv api.NodeManagement_LogsClient) (res []*cpb.LogEntry) {
+func drainLogs(t *testing.T, srv api.NodeManagement_LogsClient) (res []*lpb.LogEntry) {
 	t.Helper()
 	for {
 		ev, err := srv.Recv()
@@ -153,12 +154,12 @@
 	}
 	for i, te := range []struct {
 		req  *api.GetLogsRequest
-		want []*cpb.LogEntry
+		want []*lpb.LogEntry
 	}{
 		{
 			// Test all backlog.
 			req: mkReq("main.roleserver.kubernetes", -1),
-			want: []*cpb.LogEntry{
+			want: []*lpb.LogEntry{
 				mkLeveledEntry("main.roleserver.kubernetes", "i", "Starting kubernetes..."),
 				mkLeveledEntry("main.roleserver.kubernetes", "i", "Kubernetes version: 1.21.37"),
 			},
@@ -166,7 +167,7 @@
 		{
 			// Test exact backlog.
 			req: mkReq("main.roleserver.kubernetes", 1),
-			want: []*cpb.LogEntry{
+			want: []*lpb.LogEntry{
 				mkLeveledEntry("main.roleserver.kubernetes", "i", "Kubernetes version: 1.21.37"),
 			},
 		},
@@ -178,7 +179,7 @@
 		{
 			// Test recursion with backlog.
 			req: mkRecursive(mkReq("main.roleserver", 2)),
-			want: []*cpb.LogEntry{
+			want: []*lpb.LogEntry{
 				mkLeveledEntry("main.roleserver.kubernetes", "i", "Kubernetes version: 1.21.37"),
 				mkLeveledEntry("main.roleserver.controlplane", "i", "Starting etcd..."),
 			},
@@ -186,7 +187,7 @@
 		{
 			// Test invalid utf-8 in log data
 			req: mkReq("main.weirdo", 1),
-			want: []*cpb.LogEntry{
+			want: []*lpb.LogEntry{
 				mkLeveledEntry("main.weirdo", "i", "Here comes some invalid utf-8: a<INVALID>z"),
 			},
 		},
@@ -234,7 +235,7 @@
 	}
 
 	// Pipe returned logs into a channel for analysis.
-	logC := make(chan *cpb.LogEntry)
+	logC := make(chan *lpb.LogEntry)
 	go func() {
 		for {
 			ev, err := srv.Recv()
@@ -292,7 +293,7 @@
 
 	for i, te := range []struct {
 		req  *api.GetLogsRequest
-		want []*cpb.LogEntry
+		want []*lpb.LogEntry
 	}{
 		// Case 0: request given level
 		{
@@ -304,13 +305,13 @@
 					{
 						Filter: &cpb.LogFilter_LeveledWithMinimumSeverity_{
 							LeveledWithMinimumSeverity: &cpb.LogFilter_LeveledWithMinimumSeverity{
-								Minimum: cpb.LeveledLogSeverity_WARNING,
+								Minimum: lpb.LeveledLogSeverity_WARNING,
 							},
 						},
 					},
 				},
 			},
-			want: []*cpb.LogEntry{
+			want: []*lpb.LogEntry{
 				mkLeveledEntry("main", "w", "Something failed!"),
 				mkLeveledEntry("main", "e", "Something failed very hard!"),
 			},
@@ -329,7 +330,7 @@
 					},
 				},
 			},
-			want: []*cpb.LogEntry{
+			want: []*lpb.LogEntry{
 				mkRawEntry("main", "medium rare"),
 			},
 		},
@@ -347,7 +348,7 @@
 					},
 				},
 			},
-			want: []*cpb.LogEntry{
+			want: []*lpb.LogEntry{
 				mkLeveledEntry("main", "i", "Hello"),
 				mkLeveledEntry("main", "i", "Starting..."),
 				mkLeveledEntry("main", "w", "Something failed!"),
diff --git a/metropolis/pkg/logbuffer/BUILD.bazel b/metropolis/pkg/logbuffer/BUILD.bazel
index e890df8..8433802 100644
--- a/metropolis/pkg/logbuffer/BUILD.bazel
+++ b/metropolis/pkg/logbuffer/BUILD.bazel
@@ -8,7 +8,7 @@
     ],
     importpath = "source.monogon.dev/metropolis/pkg/logbuffer",
     visibility = ["//metropolis:__subpackages__"],
-    deps = ["//metropolis/proto/common"],
+    deps = ["//metropolis/pkg/logtree/proto"],
 )
 
 go_test(
diff --git a/metropolis/pkg/logbuffer/linebuffer.go b/metropolis/pkg/logbuffer/linebuffer.go
index 92b70e9..3892e0c 100644
--- a/metropolis/pkg/logbuffer/linebuffer.go
+++ b/metropolis/pkg/logbuffer/linebuffer.go
@@ -22,7 +22,7 @@
 	"strings"
 	"sync"
 
-	cpb "source.monogon.dev/metropolis/proto/common"
+	lpb "source.monogon.dev/metropolis/pkg/logtree/proto"
 )
 
 // Line is a line stored in the log buffer - a string, that has been perhaps
@@ -47,15 +47,15 @@
 }
 
 // ProtoLog returns a Logging-specific protobuf structure.
-func (l *Line) ProtoLog() *cpb.LogEntry_Raw {
-	return &cpb.LogEntry_Raw{
+func (l *Line) ProtoLog() *lpb.LogEntry_Raw {
+	return &lpb.LogEntry_Raw{
 		Data:           l.Data,
 		OriginalLength: int64(l.OriginalLength),
 	}
 }
 
 // LineFromLogProto converts a Logging-specific protobuf message back into a Line.
-func LineFromLogProto(raw *cpb.LogEntry_Raw) (*Line, error) {
+func LineFromLogProto(raw *lpb.LogEntry_Raw) (*Line, error) {
 	if raw.OriginalLength < int64(len(raw.Data)) {
 		return nil, fmt.Errorf("original_length smaller than length of data")
 	}
diff --git a/metropolis/pkg/logtree/BUILD.bazel b/metropolis/pkg/logtree/BUILD.bazel
index 595e8cf..a2f86ad 100644
--- a/metropolis/pkg/logtree/BUILD.bazel
+++ b/metropolis/pkg/logtree/BUILD.bazel
@@ -24,7 +24,7 @@
     visibility = ["//visibility:public"],
     deps = [
         "//metropolis/pkg/logbuffer",
-        "//metropolis/proto/common",
+        "//metropolis/pkg/logtree/proto",
         "@com_github_mitchellh_go_wordwrap//:go-wordwrap",
         "@org_golang_google_grpc//grpclog",
         "@org_golang_google_protobuf//types/known/timestamppb",
diff --git a/metropolis/pkg/logtree/leveled.go b/metropolis/pkg/logtree/leveled.go
index 5c57222..0facbb1 100644
--- a/metropolis/pkg/logtree/leveled.go
+++ b/metropolis/pkg/logtree/leveled.go
@@ -19,7 +19,7 @@
 import (
 	"fmt"
 
-	cpb "source.monogon.dev/metropolis/proto/common"
+	lpb "source.monogon.dev/metropolis/pkg/logtree/proto"
 )
 
 // LeveledLogger is a generic interface for glog-style logging. There are four
@@ -144,32 +144,32 @@
 	}
 }
 
-func SeverityFromProto(s cpb.LeveledLogSeverity) (Severity, error) {
+func SeverityFromProto(s lpb.LeveledLogSeverity) (Severity, error) {
 	switch s {
-	case cpb.LeveledLogSeverity_INFO:
+	case lpb.LeveledLogSeverity_INFO:
 		return INFO, nil
-	case cpb.LeveledLogSeverity_WARNING:
+	case lpb.LeveledLogSeverity_WARNING:
 		return WARNING, nil
-	case cpb.LeveledLogSeverity_ERROR:
+	case lpb.LeveledLogSeverity_ERROR:
 		return ERROR, nil
-	case cpb.LeveledLogSeverity_FATAL:
+	case lpb.LeveledLogSeverity_FATAL:
 		return FATAL, nil
 	default:
 		return "", fmt.Errorf("unknown severity value %d", s)
 	}
 }
 
-func (s Severity) ToProto() cpb.LeveledLogSeverity {
+func (s Severity) ToProto() lpb.LeveledLogSeverity {
 	switch s {
 	case INFO:
-		return cpb.LeveledLogSeverity_INFO
+		return lpb.LeveledLogSeverity_INFO
 	case WARNING:
-		return cpb.LeveledLogSeverity_WARNING
+		return lpb.LeveledLogSeverity_WARNING
 	case ERROR:
-		return cpb.LeveledLogSeverity_ERROR
+		return lpb.LeveledLogSeverity_ERROR
 	case FATAL:
-		return cpb.LeveledLogSeverity_FATAL
+		return lpb.LeveledLogSeverity_FATAL
 	default:
-		return cpb.LeveledLogSeverity_INVALID
+		return lpb.LeveledLogSeverity_INVALID
 	}
 }
diff --git a/metropolis/pkg/logtree/leveled_payload.go b/metropolis/pkg/logtree/leveled_payload.go
index e2b2ff1..b4a0630 100644
--- a/metropolis/pkg/logtree/leveled_payload.go
+++ b/metropolis/pkg/logtree/leveled_payload.go
@@ -24,7 +24,7 @@
 
 	tpb "google.golang.org/protobuf/types/known/timestamppb"
 
-	cpb "source.monogon.dev/metropolis/proto/common"
+	lpb "source.monogon.dev/metropolis/pkg/logtree/proto"
 )
 
 // LeveledPayload is a log entry for leveled logs (as per leveled.go). It contains
@@ -114,8 +114,8 @@
 func (p *LeveledPayload) Severity() Severity { return p.severity }
 
 // Proto converts a LeveledPayload to protobuf format.
-func (p *LeveledPayload) Proto() *cpb.LogEntry_Leveled {
-	return &cpb.LogEntry_Leveled{
+func (p *LeveledPayload) Proto() *lpb.LogEntry_Leveled {
+	return &lpb.LogEntry_Leveled{
 		Lines:     p.Messages(),
 		Timestamp: tpb.New(p.Timestamp()),
 		Severity:  p.Severity().ToProto(),
@@ -124,7 +124,7 @@
 }
 
 // LeveledPayloadFromProto parses a protobuf message into the internal format.
-func LeveledPayloadFromProto(p *cpb.LogEntry_Leveled) (*LeveledPayload, error) {
+func LeveledPayloadFromProto(p *lpb.LogEntry_Leveled) (*LeveledPayload, error) {
 	severity, err := SeverityFromProto(p.Severity)
 	if err != nil {
 		return nil, fmt.Errorf("could not convert severity: %w", err)
diff --git a/metropolis/pkg/logtree/logtree_entry.go b/metropolis/pkg/logtree/logtree_entry.go
index 6bd8752..d1c700e 100644
--- a/metropolis/pkg/logtree/logtree_entry.go
+++ b/metropolis/pkg/logtree/logtree_entry.go
@@ -23,7 +23,7 @@
 	"github.com/mitchellh/go-wordwrap"
 
 	"source.monogon.dev/metropolis/pkg/logbuffer"
-	cpb "source.monogon.dev/metropolis/proto/common"
+	lpb "source.monogon.dev/metropolis/pkg/logtree/proto"
 )
 
 // LogEntry contains a log entry, combining both leveled and raw logging into a
@@ -207,19 +207,19 @@
 
 // Proto converts this LogEntry to proto. Returned value may be nil if given
 // LogEntry is invalid, eg. contains neither a Raw nor Leveled entry.
-func (l *LogEntry) Proto() *cpb.LogEntry {
-	p := &cpb.LogEntry{
+func (l *LogEntry) Proto() *lpb.LogEntry {
+	p := &lpb.LogEntry{
 		Dn: string(l.DN),
 	}
 	switch {
 	case l.Leveled != nil:
 		leveled := l.Leveled
-		p.Kind = &cpb.LogEntry_Leveled_{
+		p.Kind = &lpb.LogEntry_Leveled_{
 			Leveled: leveled.Proto(),
 		}
 	case l.Raw != nil:
 		raw := l.Raw
-		p.Kind = &cpb.LogEntry_Raw_{
+		p.Kind = &lpb.LogEntry_Raw_{
 			Raw: raw.ProtoLog(),
 		}
 	default:
@@ -231,7 +231,7 @@
 // LogEntryFromProto parses a proto LogEntry back into internal structure.
 // This can be used in log proto API consumers to easily print received log
 // entries.
-func LogEntryFromProto(l *cpb.LogEntry) (*LogEntry, error) {
+func LogEntryFromProto(l *lpb.LogEntry) (*LogEntry, error) {
 	dn := DN(l.Dn)
 	if _, err := dn.Path(); err != nil {
 		return nil, fmt.Errorf("could not convert DN: %w", err)
@@ -240,13 +240,13 @@
 		DN: dn,
 	}
 	switch inner := l.Kind.(type) {
-	case *cpb.LogEntry_Leveled_:
+	case *lpb.LogEntry_Leveled_:
 		leveled, err := LeveledPayloadFromProto(inner.Leveled)
 		if err != nil {
 			return nil, fmt.Errorf("could not convert leveled entry: %w", err)
 		}
 		res.Leveled = leveled
-	case *cpb.LogEntry_Raw_:
+	case *lpb.LogEntry_Raw_:
 		line, err := logbuffer.LineFromLogProto(inner.Raw)
 		if err != nil {
 			return nil, fmt.Errorf("could not convert raw entry: %w", err)
diff --git a/metropolis/pkg/logtree/proto/BUILD.bazel b/metropolis/pkg/logtree/proto/BUILD.bazel
new file mode 100644
index 0000000..e7f8c82
--- /dev/null
+++ b/metropolis/pkg/logtree/proto/BUILD.bazel
@@ -0,0 +1,24 @@
+load("@rules_proto//proto:defs.bzl", "proto_library")
+load("@io_bazel_rules_go//go:def.bzl", "go_library")
+load("@io_bazel_rules_go//proto:def.bzl", "go_proto_library")
+
+proto_library(
+    name = "proto_proto",
+    srcs = ["logtree.proto"],
+    visibility = ["//visibility:public"],
+    deps = ["@com_google_protobuf//:timestamp_proto"],
+)
+
+go_proto_library(
+    name = "proto_go_proto",
+    importpath = "source.monogon.dev/metropolis/pkg/logtree/proto",
+    proto = ":proto_proto",
+    visibility = ["//visibility:public"],
+)
+
+go_library(
+    name = "proto",
+    embed = [":proto_go_proto"],
+    importpath = "source.monogon.dev/metropolis/pkg/logtree/proto",
+    visibility = ["//visibility:public"],
+)
diff --git a/metropolis/pkg/logtree/proto/gomod-generated-placeholder.go b/metropolis/pkg/logtree/proto/gomod-generated-placeholder.go
new file mode 100644
index 0000000..92256db
--- /dev/null
+++ b/metropolis/pkg/logtree/proto/gomod-generated-placeholder.go
@@ -0,0 +1 @@
+package proto
diff --git a/metropolis/pkg/logtree/proto/logtree.proto b/metropolis/pkg/logtree/proto/logtree.proto
new file mode 100644
index 0000000..7586187
--- /dev/null
+++ b/metropolis/pkg/logtree/proto/logtree.proto
@@ -0,0 +1,59 @@
+// 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.
+
+syntax = "proto3";
+package metropolis.pkg.logtree.proto;
+option go_package = "source.monogon.dev/metropolis/pkg/logtree/proto";
+
+import "google/protobuf/timestamp.proto";
+
+// Severity level corresponding to //metropolis/pkg/logtree.Severity.
+enum LeveledLogSeverity {
+  INVALID = 0;
+  INFO = 1;
+  WARNING = 2;
+  ERROR = 3;
+  FATAL = 4;
+}
+
+// LogEntry corresponding to logtree.LogEntry in //metropolis/pkg/logtree.
+message LogEntry {
+  // A leveled log entry emitted from a compatible system, eg. Metorpolis code
+  // or a klog-parsed line.
+  message Leveled {
+    repeated string lines = 1;
+    google.protobuf.Timestamp timestamp = 2;
+    LeveledLogSeverity severity = 3;
+    // Source of the error, expressed as file:line.
+    string location = 4;
+  }
+  // Raw log entry, captured from an external system without parting. Might
+  // contain some timestamp/level/origin information embedded in data. Data
+  // contained within should be treated as unsanitized external data.
+  message Raw {
+    string data = 1;
+    // Original length of line, set if data was truncated.
+    int64 original_length = 2;
+  }
+
+  // Origin DN (Distinguished Name), a unique identifier which is provided by
+  // the supervisor system.
+  string dn = 1;
+  oneof kind {
+    Leveled leveled = 2;
+    Raw raw = 3;
+  }
+}
diff --git a/metropolis/proto/api/BUILD.bazel b/metropolis/proto/api/BUILD.bazel
index a1d681b..66ab06a 100644
--- a/metropolis/proto/api/BUILD.bazel
+++ b/metropolis/proto/api/BUILD.bazel
@@ -12,6 +12,7 @@
     ],
     visibility = ["//visibility:public"],
     deps = [
+        "//metropolis/pkg/logtree/proto:proto_proto",
         "//metropolis/proto/common:common_proto",
         "//metropolis/proto/ext:ext_proto",
         "//net/proto:net_proto_proto",
@@ -26,6 +27,7 @@
     proto = ":api_proto",
     visibility = ["//visibility:public"],
     deps = [
+        "//metropolis/pkg/logtree/proto",
         "//metropolis/proto/common",
         "//metropolis/proto/ext",
         "//net/proto",
diff --git a/metropolis/proto/api/management.proto b/metropolis/proto/api/management.proto
index bdb4a03..bd7bd5b 100644
--- a/metropolis/proto/api/management.proto
+++ b/metropolis/proto/api/management.proto
@@ -4,6 +4,7 @@
 
 import "google/protobuf/duration.proto";
 
+import "metropolis/pkg/logtree/proto/logtree.proto";
 import "metropolis/proto/common/common.proto";
 import "metropolis/proto/ext/authorization.proto";
 
@@ -386,11 +387,11 @@
 message GetLogsResponse {
   // Entries from the requested historical entries (via WithBackLog). They will
   // all be served before the first stream_entries are served (if any).
-  repeated metropolis.proto.common.LogEntry backlog_entries = 1;
+  repeated metropolis.pkg.logtree.proto.LogEntry backlog_entries = 1;
   // Entries streamed as they arrive. Currently no server-side buffering is
   // enabled, instead every line is served as early as it arrives. However, this
   // might change in the future, so this behaviour cannot be depended upon.
-  repeated metropolis.proto.common.LogEntry stream_entries = 2;
+  repeated metropolis.pkg.logtree.proto.LogEntry stream_entries = 2;
 }
 
 enum ActivationMode {
diff --git a/metropolis/proto/common/BUILD.bazel b/metropolis/proto/common/BUILD.bazel
index 05c4995..10f7f84 100644
--- a/metropolis/proto/common/BUILD.bazel
+++ b/metropolis/proto/common/BUILD.bazel
@@ -7,6 +7,7 @@
     srcs = ["common.proto"],
     visibility = ["//metropolis:__subpackages__"],
     deps = [
+        "//metropolis/pkg/logtree/proto:proto_proto",
         "//version/spec:spec_proto",
         "@com_google_protobuf//:timestamp_proto",
     ],
@@ -17,7 +18,10 @@
     importpath = "source.monogon.dev/metropolis/proto/common",
     proto = ":common_proto",
     visibility = ["//metropolis:__subpackages__"],
-    deps = ["//version/spec"],
+    deps = [
+        "//metropolis/pkg/logtree/proto",
+        "//version/spec",
+    ],
 )
 
 go_library(
diff --git a/metropolis/proto/common/common.proto b/metropolis/proto/common/common.proto
index 653b0d2..b870d20 100644
--- a/metropolis/proto/common/common.proto
+++ b/metropolis/proto/common/common.proto
@@ -21,6 +21,8 @@
 import "google/protobuf/timestamp.proto";
 import "version/spec/spec.proto";
 
+import "metropolis/pkg/logtree/proto/logtree.proto";
+
 // NodeRoles are the possible roles that a Metropolis Node should run within the
 // cluster. These are configured by the cluster and can be retrieved through the
 // Curator.
@@ -196,15 +198,6 @@
     repeated Prefix prefixes = 2;
 }
 
-// Severity level corresponding to //metropolis/pkg/logtree.Severity.
-enum LeveledLogSeverity {
-    INVALID = 0;
-    INFO = 1;
-    WARNING = 2;
-    ERROR = 3;
-    FATAL = 4;
-}
-
 // Filter set when requesting logs for a given DN. This message is equivalent to
 // the following GADT enum:
 // data LogFilter = WithChildren
@@ -231,7 +224,7 @@
     // If leveled logs are returned, all entries at severity lower than `minimum`
     // will be discarded.
     message LeveledWithMinimumSeverity {
-        LeveledLogSeverity minimum = 1;
+        metropolis.pkg.logtree.proto.LeveledLogSeverity minimum = 1;
     }
     oneof filter {
         WithChildren with_children = 1;
@@ -241,34 +234,6 @@
     }
 }
 
-// LogEntry corresponding to logtree.LogEntry in //metropolis/pkg/logtree.
-message LogEntry {
-    // A leveled log entry emitted from a compatible system, eg. Metorpolis code
-    // or a klog-parsed line.
-    message Leveled {
-        repeated string lines = 1;
-        google.protobuf.Timestamp timestamp = 2;
-        LeveledLogSeverity severity = 3;
-        // Source of the error, expressed as file:line.
-        string location = 4;
-    }
-    // Raw log entry, captured from an external system without parting. Might
-    // contain some timestamp/level/origin information embedded in data. Data
-    // contained within should be treated as unsanitized external data.
-    message Raw {
-        string data = 1;
-        // Original length of line, set if data was truncated.
-        int64 original_length = 2;
-    }
-
-    // Origin DN.
-    string dn = 1;
-    oneof kind {
-        Leveled leveled = 2;
-        Raw raw = 3;
-    }
-}
-
 // ClusterConfiguration contains the entirety of the user-configurable behaviour
 // of the cluster that is scoped to the entirety of the cluster (vs. per-node
 // configuration, which is kept alongside Node).