blob: 442d456b4095591dcf117f1b1e36a6caffb20e2d [file] [log] [blame]
Serge Bazanskiedf5c4f2020-11-25 13:45:31 +01001// Copyright 2020 The Monogon Project Authors.
2//
3// SPDX-License-Identifier: Apache-2.0
4//
5// Licensed under the Apache License, Version 2.0 (the "License");
6// you may not use this file except in compliance with the License.
7// You may obtain a copy of the License at
8//
9// http://www.apache.org/licenses/LICENSE-2.0
10//
11// Unless required by applicable law or agreed to in writing, software
12// distributed under the License is distributed on an "AS IS" BASIS,
13// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14// See the License for the specific language governing permissions and
15// limitations under the License.
16
17package logtree
18
19import (
20 "fmt"
21 "strings"
22
Serge Bazanski31370b02021-01-07 16:31:14 +010023 "source.monogon.dev/metropolis/pkg/logbuffer"
24 apb "source.monogon.dev/metropolis/proto/api"
Serge Bazanskiedf5c4f2020-11-25 13:45:31 +010025)
26
Serge Bazanski216fe7b2021-05-21 18:36:16 +020027// LogEntry contains a log entry, combining both leveled and raw logging into a
28// single stream of events. A LogEntry will contain exactly one of either
29// LeveledPayload or RawPayload.
Serge Bazanskiedf5c4f2020-11-25 13:45:31 +010030type LogEntry struct {
31 // If non-nil, this is a leveled logging entry.
32 Leveled *LeveledPayload
33 // If non-nil, this is a raw logging entry line.
34 Raw *logbuffer.Line
35 // DN from which this entry was logged.
36 DN DN
37}
38
Serge Bazanski216fe7b2021-05-21 18:36:16 +020039// String returns a canonical representation of this payload as a single string
40// prefixed with metadata. If the entry is a leveled log entry that originally was
41// logged with newlines this representation will also contain newlines, with each
42// original message part prefixed by the metadata. For an alternative call that
43// will instead return a canonical prefix and a list of lines in the message, see
44// Strings().
Serge Bazanskiedf5c4f2020-11-25 13:45:31 +010045func (l *LogEntry) String() string {
46 if l.Leveled != nil {
47 prefix, messages := l.Leveled.Strings()
48 res := make([]string, len(messages))
49 for i, m := range messages {
50 res[i] = fmt.Sprintf("%-32s %s%s", l.DN, prefix, m)
51 }
52 return strings.Join(res, "\n")
53 }
54 if l.Raw != nil {
55 return fmt.Sprintf("%-32s R %s", l.DN, l.Raw)
56 }
57 return "INVALID"
58}
59
Serge Bazanski216fe7b2021-05-21 18:36:16 +020060// Strings returns the canonical representation of this payload split into a
61// prefix and all lines that were contained in the original message. This is
62// meant to be displayed to the user by showing the prefix before each line,
63// concatenated together - possibly in a table form with the prefixes all
64// unified with a rowspan- like mechanism.
Serge Bazanskiedf5c4f2020-11-25 13:45:31 +010065//
66// For example, this function can return:
67// prefix = "root.foo.bar I1102 17:20:06.921395 0 foo.go:42] "
68// lines = []string{"current tags:", " - one", " - two"}
69//
70// With this data, the result should be presented to users this way in text form:
71// root.foo.bar I1102 17:20:06.921395 foo.go:42] current tags:
72// root.foo.bar I1102 17:20:06.921395 foo.go:42] - one
73// root.foo.bar I1102 17:20:06.921395 foo.go:42] - two
74//
75// Or, in a table layout:
Serge Bazanski216fe7b2021-05-21 18:36:16 +020076// .----------------------------------------------------------------------.
77// | root.foo.bar I1102 17:20:06.921395 foo.go:42] : current tags: |
78// | :------------------|
79// | : - one |
80// | :------------------|
81// | : - two |
82// '----------------------------------------------------------------------'
83
Serge Bazanskiedf5c4f2020-11-25 13:45:31 +010084func (l *LogEntry) Strings() (prefix string, lines []string) {
85 if l.Leveled != nil {
86 prefix, messages := l.Leveled.Strings()
87 prefix = fmt.Sprintf("%-32s %s", l.DN, prefix)
88 return prefix, messages
89 }
90 if l.Raw != nil {
91 return fmt.Sprintf("%-32s R ", l.DN), []string{l.Raw.Data}
92 }
93 return "INVALID ", []string{"INVALID"}
94}
95
Serge Bazanski216fe7b2021-05-21 18:36:16 +020096// Convert this LogEntry to proto. Returned value may be nil if given LogEntry is
97// invalid, eg. contains neither a Raw nor Leveled entry.
Serge Bazanskiedf5c4f2020-11-25 13:45:31 +010098func (l *LogEntry) Proto() *apb.LogEntry {
99 p := &apb.LogEntry{
100 Dn: string(l.DN),
101 }
102 switch {
103 case l.Leveled != nil:
104 leveled := l.Leveled
105 p.Kind = &apb.LogEntry_Leveled_{
106 Leveled: leveled.Proto(),
107 }
108 case l.Raw != nil:
109 raw := l.Raw
110 p.Kind = &apb.LogEntry_Raw_{
111 Raw: raw.ProtoLog(),
112 }
113 default:
114 return nil
115 }
116 return p
117}
118
Serge Bazanski216fe7b2021-05-21 18:36:16 +0200119// Parse a proto LogEntry back into internal structure. This can be used in log
120// proto API consumers to easily print received log entries.
Serge Bazanskiedf5c4f2020-11-25 13:45:31 +0100121func LogEntryFromProto(l *apb.LogEntry) (*LogEntry, error) {
122 dn := DN(l.Dn)
123 if _, err := dn.Path(); err != nil {
124 return nil, fmt.Errorf("could not convert DN: %w", err)
125 }
126 res := &LogEntry{
127 DN: dn,
128 }
129 switch inner := l.Kind.(type) {
130 case *apb.LogEntry_Leveled_:
131 leveled, err := LeveledPayloadFromProto(inner.Leveled)
132 if err != nil {
133 return nil, fmt.Errorf("could not convert leveled entry: %w", err)
134 }
135 res.Leveled = leveled
136 case *apb.LogEntry_Raw_:
137 line, err := logbuffer.LineFromLogProto(inner.Raw)
138 if err != nil {
139 return nil, fmt.Errorf("could not convert raw entry: %w", err)
140 }
141 res.Raw = line
142 default:
143 return nil, fmt.Errorf("proto has neither Leveled nor Raw set")
144 }
145 return res, nil
146}