core: plug logtree into NodeDebugService

This introduces a new Proto API for accessing debug logs. Currently this
is implemented to be used by the debug service. However, these proto
definitions will likely be reused for production cluster APIs.

The implementation mostly consists of adding the proto, implementing
to/from conversion methods, and altering the debug service to use the
new API.

We also move all of the debug service implementation into a separate file,
to slightly clean up main.go. This produces an unfortunately colorful
diff, but it's just moving code around.

Test Plan: Manually tested using the dbg tool. We currently don't properly test the debug service. I suppose we should do that for the production cluster APIs, and just keep on going for now.

X-Origin-Diff: phab/D649
GitOrigin-RevId: ac454681e4b72b2876e313b3aeababa179eb1fa3
diff --git a/core/proto/api/debug.proto b/core/proto/api/debug.proto
index 74d314a..b0bbb57 100644
--- a/core/proto/api/debug.proto
+++ b/core/proto/api/debug.proto
@@ -26,8 +26,22 @@
 service NodeDebugService {
     // GetDebugKubeconfig issues kubeconfigs with arbitrary identities and groups for debugging
     rpc GetDebugKubeconfig(GetDebugKubeconfigRequest) returns (GetDebugKubeconfigResponse);
-    // GetComponentLogs dumps various log ringbuffers for binaries that we run.
-    rpc GetComponentLogs(GetComponentLogsRequest) returns (GetComponentLogsResponse);
+
+    // GetLogs Returns historical and/or streaming logs for a given DN with given filters from the system global
+    // LogTree.
+    //
+    // For more information about this API, see //core/pkg/logtree. But, in summary:
+    //   - All logging is performed to a DN (distinguished name), which is a dot-delimited string like foo.bar.baz.
+    //   - Log entries can be either raw (coming from unstructured logging from an external service, like a running
+    //     process) or leveled (emitted by Smalltown code with a source line, timestamp, and severity).
+    //   - The DNs form a tree of logging nodes - and when requesting logs, a given subtree of DNs can be requested,
+    //     instead of just a given DN.
+    //   - All supervised processes live at `root.<supervisor DN>`. For more example paths, see the console logs of
+    //     a running Smalltown instance, or request all logs (at DN "").
+    //
+    // TODO(q3k): move method and its related messages to the non-debug node endpoint once we have one.
+    rpc GetLogs(GetLogsRequest) returns (stream GetLogsResponse);
+
     // GetGoldenTicket requests a 'golden ticket' which can be used to enroll any node into the cluster.
     // This bypasses integrity checks.
     rpc GetGoldenTicket(GetGoldenTicketRequest) returns (GetGoldenTicketResponse);
@@ -43,14 +57,100 @@
     string debug_kubeconfig = 1;
 }
 
-message GetComponentLogsRequest {
-    // For supported paths see core/internal/node/debug.go
-    repeated string component_path = 1;
-    uint32 tail_lines = 2; // 0 = whole ring buffer
+// Severity level corresponding to //core/pkg/logtree.Severity.
+enum LeveledLogSeverity {
+    INVALID = 0;
+    INFO = 1;
+    WARNING = 2;
+    ERROR = 3;
+    FATAL = 4;
 }
 
-message GetComponentLogsResponse {
-    repeated string line = 1;
+// Filter set when requesting logs for a given DN. This message is equivalent to the following GADT enum:
+// data LogFilter = WithChildren
+//                | OnlyRaw
+//                | OnlyLeveled
+//                | LeveledWithMinimumSeverity(Severity)
+//
+// Multiple LogFilters can be chained/combined when requesting logs, as long as they do not conflict.
+message LogFilter {
+    // Entries will be returned not only for the given DN, but all child DNs as well. For instance, if the
+    // requested DN is foo, entries logged to foo, foo.bar and foo.bar.baz will all be returned.
+    message WithChildren {
+    }
+    // Only raw logging entries will be returned. Conflicts with OnlyLeveled filters.
+    message OnlyRaw {
+    }
+    // Only leveled logging entries will be returned. Conflicts with OnlyRaw filters.
+    message OnlyLeveled {
+    }
+    // If leveled logs are returned, all entries at severity lower than `minimum` will be discarded.
+    message LeveledWithMinimumSeverity {
+        LeveledLogSeverity minimum = 1;
+    }
+    oneof filter {
+        WithChildren with_children = 1;
+        OnlyRaw only_raw = 3;
+        OnlyLeveled only_leveled = 4;
+        LeveledWithMinimumSeverity leveled_with_minimum_severity = 5;
+    }
+}
+
+message GetLogsRequest {
+    // DN from which to request logs. All supervised runnables live at `root.`, the init code lives at `init.`.
+    string dn = 1;
+    // Filters to apply to returned data.
+    repeated LogFilter filters = 2;
+
+    enum BacklogMode {
+        BACKLOG_INVALID = 0;
+        // No historic data will be returned.
+        BACKLOG_DISABLE = 1;
+        // All available historic data will be returned.
+        BACKLOG_ALL = 2;
+        // At most backlog_count entries will be returned, if available.
+        BACKLOG_COUNT = 3;
+    }
+    BacklogMode backlog_mode = 3;
+    int64 backlog_count = 4;
+
+    enum StreamMode {
+        STREAM_INVALID = 0;
+        // No streaming entries, gRPC stream will be closed as soon as all backlog data is served.
+        STREAM_DISABLE = 1;
+        // Entries will be streamed as early as available right after all backlog data is served.
+        STREAM_UNBUFFERED = 2;
+    }
+    StreamMode stream_mode = 5;
+}
+
+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 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 LogEntry stream_entries = 2;
+}
+
+message LogEntry {
+    message Leveled {
+        string message = 1;
+        int64 timestamp = 2;
+        LeveledLogSeverity severity = 3;
+        string location = 4;
+    }
+    message Raw {
+        string data = 1;
+        int64 original_length = 2;
+    }
+
+    string dn = 1;
+    oneof kind {
+        Leveled leveled = 2;
+        Raw raw = 3;
+    }
 }
 
 message GetGoldenTicketRequest {