blob: 968a5a9ff229d5768ece921cd762f7484b707c61 [file] [log] [blame]
Serge Bazanski5faa2fc2020-09-07 14:09:30 +02001// 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 "sync"
Serge Bazanskif68153c2020-10-26 13:54:37 +010023
Serge Bazanski31370b02021-01-07 16:31:14 +010024 "source.monogon.dev/metropolis/pkg/logbuffer"
Serge Bazanski5faa2fc2020-09-07 14:09:30 +020025)
26
Serge Bazanski216fe7b2021-05-21 18:36:16 +020027// LogTree is a tree-shaped logging system. For more information, see the package-
28// level documentation.
Serge Bazanski5faa2fc2020-09-07 14:09:30 +020029type LogTree struct {
30 // journal is the tree's journal, storing all log data and managing subscribers.
31 journal *journal
Serge Bazanski216fe7b2021-05-21 18:36:16 +020032 // root is the root node of the actual tree of the log tree. The nodes contain per-
33 // DN configuration options, notably the current verbosity level of that DN.
Serge Bazanski5faa2fc2020-09-07 14:09:30 +020034 root *node
35}
36
37func New() *LogTree {
38 lt := &LogTree{
39 journal: newJournal(),
40 }
41 lt.root = newNode(lt, "")
42 return lt
43}
44
Serge Bazanski216fe7b2021-05-21 18:36:16 +020045// node represents a given DN as a discrete 'logger'. It implements the
46// LeveledLogger interface for log publishing, entries from which it passes over to
47// the logtree's journal.
Serge Bazanski5faa2fc2020-09-07 14:09:30 +020048type node struct {
49 // dn is the DN which this node represents (or "" if this is the root node).
50 dn DN
51 // tree is the LogTree to which this node belongs.
52 tree *LogTree
Serge Bazanski216fe7b2021-05-21 18:36:16 +020053 // verbosity is the current verbosity level of this DN/node, affecting .V(n)
54 // LeveledLogger calls
Serge Bazanskif68153c2020-10-26 13:54:37 +010055 verbosity VerbosityLevel
56 rawLineBuffer *logbuffer.LineBuffer
Serge Bazanski5faa2fc2020-09-07 14:09:30 +020057
58 // mu guards children.
59 mu sync.Mutex
Serge Bazanski216fe7b2021-05-21 18:36:16 +020060 // children is a map of DN-part to a children node in the logtree. A DN-part is a
61 // string representing a part of the DN between the deliming dots, as returned by
62 // DN.Path.
Serge Bazanski5faa2fc2020-09-07 14:09:30 +020063 children map[string]*node
64}
65
Serge Bazanski216fe7b2021-05-21 18:36:16 +020066// newNode returns a node at a given DN in the LogTree - but doesn't set up the
67// LogTree to insert it accordingly.
Serge Bazanski5faa2fc2020-09-07 14:09:30 +020068func newNode(tree *LogTree, dn DN) *node {
69 n := &node{
70 dn: dn,
71 tree: tree,
72 children: make(map[string]*node),
73 }
Serge Bazanskib0272182020-11-02 18:39:44 +010074 // TODO(q3k): make this limit configurable. If this happens, or the default (1024) gets changes, max chunk size
75 // calculations when serving the logs (eg. in NodeDebugService) must reflect this.
Serge Bazanskif68153c2020-10-26 13:54:37 +010076 n.rawLineBuffer = logbuffer.NewLineBuffer(1024, n.logRaw)
Serge Bazanski5faa2fc2020-09-07 14:09:30 +020077 return n
78}
79
Serge Bazanski216fe7b2021-05-21 18:36:16 +020080// nodeByDN returns the LogTree node corresponding to a given DN. If either the
81// node or some of its parents do not exist they will be created as needed.
Serge Bazanski5faa2fc2020-09-07 14:09:30 +020082func (l *LogTree) nodeByDN(dn DN) (*node, error) {
83 traversal, err := newTraversal(dn)
84 if err != nil {
85 return nil, fmt.Errorf("traversal failed: %w", err)
86 }
87 return traversal.execute(l.root), nil
88}
89
Serge Bazanski216fe7b2021-05-21 18:36:16 +020090// nodeTraversal represents a request to traverse the LogTree in search of a given
91// node by DN.
Serge Bazanski5faa2fc2020-09-07 14:09:30 +020092type nodeTraversal struct {
93 // want is the DN of the node's that requested to be found.
94 want DN
Serge Bazanski216fe7b2021-05-21 18:36:16 +020095 // current is the path already taken to find the node, in the form of DN parts. It
96 // starts out as want.Parts() and progresses to become empty as the traversal
97 // continues.
Serge Bazanski5faa2fc2020-09-07 14:09:30 +020098 current []string
Serge Bazanski216fe7b2021-05-21 18:36:16 +020099 // left is the path that's still needed to be taken in order to find the node, in
100 // the form of DN parts. It starts out empty and progresses to become wants.Parts()
101 // as the traversal continues.
Serge Bazanski5faa2fc2020-09-07 14:09:30 +0200102 left []string
103}
104
Serge Bazanski216fe7b2021-05-21 18:36:16 +0200105// next adjusts the traversal's current/left slices to the next element of the
106// traversal, returns the part that's now being looked for (or "" if the traveral
107// is done) and the full DN of the element that's being looked for.
Serge Bazanski5faa2fc2020-09-07 14:09:30 +0200108//
Serge Bazanski216fe7b2021-05-21 18:36:16 +0200109// For example, a traversal of foo.bar.baz will cause .next() to return the
110// following on each invocation:
Serge Bazanski5faa2fc2020-09-07 14:09:30 +0200111// - part: foo, full: foo
112// - part: bar, full: foo.bar
113// - part: baz, full: foo.bar.baz
114// - part: "", full: foo.bar.baz
115func (t *nodeTraversal) next() (part string, full DN) {
116 if len(t.left) == 0 {
117 return "", t.want
118 }
119 part = t.left[0]
120 t.current = append(t.current, part)
121 t.left = t.left[1:]
122 full = DN(strings.Join(t.current, "."))
123 return
124}
125
126// newTraversal returns a nodeTraversal fora a given wanted DN.
127func newTraversal(dn DN) (*nodeTraversal, error) {
128 parts, err := dn.Path()
129 if err != nil {
130 return nil, err
131 }
132 return &nodeTraversal{
133 want: dn,
134 left: parts,
135 }, nil
136}
137
Serge Bazanski216fe7b2021-05-21 18:36:16 +0200138// execute the traversal in order to find the node. This can only be called once
139// per traversal. Nodes will be created within the tree until the target node is
140// reached. Existing nodes will be reused. This is effectively an idempotent way of
141// accessing a node in the tree based on a traversal.
Serge Bazanski5faa2fc2020-09-07 14:09:30 +0200142func (t *nodeTraversal) execute(n *node) *node {
143 cur := n
144 for {
145 part, full := t.next()
146 if part == "" {
147 return cur
148 }
149
150 mu := &cur.mu
151 mu.Lock()
152 if _, ok := cur.children[part]; !ok {
153 cur.children[part] = newNode(n.tree, DN(full))
154 }
155 cur = cur.children[part]
156 mu.Unlock()
157 }
158}