blob: a21545f8b634078e4aab9f825fbbfe3ea62286f4 [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 Bazanskif68153c2020-10-26 13:54:37 +010027// LogTree is a tree-shaped logging system. For more information, see the package-level documentation.
Serge Bazanski5faa2fc2020-09-07 14:09:30 +020028type LogTree struct {
29 // journal is the tree's journal, storing all log data and managing subscribers.
30 journal *journal
31 // root is the root node of the actual tree of the log tree. The nodes contain per-DN configuration options, notably
32 // the current verbosity level of that DN.
33 root *node
34}
35
36func New() *LogTree {
37 lt := &LogTree{
38 journal: newJournal(),
39 }
40 lt.root = newNode(lt, "")
41 return lt
42}
43
Serge Bazanskif68153c2020-10-26 13:54:37 +010044// node represents a given DN as a discrete 'logger'. It implements the LeveledLogger interface for log publishing,
Serge Bazanski5faa2fc2020-09-07 14:09:30 +020045// entries from which it passes over to the logtree's journal.
46type node struct {
47 // dn is the DN which this node represents (or "" if this is the root node).
48 dn DN
49 // tree is the LogTree to which this node belongs.
50 tree *LogTree
51 // verbosity is the current verbosity level of this DN/node, affecting .V(n) LeveledLogger calls
Serge Bazanskif68153c2020-10-26 13:54:37 +010052 verbosity VerbosityLevel
53 rawLineBuffer *logbuffer.LineBuffer
Serge Bazanski5faa2fc2020-09-07 14:09:30 +020054
55 // mu guards children.
56 mu sync.Mutex
57 // children is a map of DN-part to a children node in the logtree. A DN-part is a string representing a part of the
58 // DN between the deliming dots, as returned by DN.Path.
59 children map[string]*node
60}
61
62// newNode returns a node at a given DN in the LogTree - but doesn't set up the LogTree to insert it accordingly.
63func newNode(tree *LogTree, dn DN) *node {
64 n := &node{
65 dn: dn,
66 tree: tree,
67 children: make(map[string]*node),
68 }
Serge Bazanskib0272182020-11-02 18:39:44 +010069 // TODO(q3k): make this limit configurable. If this happens, or the default (1024) gets changes, max chunk size
70 // calculations when serving the logs (eg. in NodeDebugService) must reflect this.
Serge Bazanskif68153c2020-10-26 13:54:37 +010071 n.rawLineBuffer = logbuffer.NewLineBuffer(1024, n.logRaw)
Serge Bazanski5faa2fc2020-09-07 14:09:30 +020072 return n
73}
74
75// nodeByDN returns the LogTree node corresponding to a given DN. If either the node or some of its parents do not
76// exist they will be created as needed.
77func (l *LogTree) nodeByDN(dn DN) (*node, error) {
78 traversal, err := newTraversal(dn)
79 if err != nil {
80 return nil, fmt.Errorf("traversal failed: %w", err)
81 }
82 return traversal.execute(l.root), nil
83}
84
85// nodeTraversal represents a request to traverse the LogTree in search of a given node by DN.
86type nodeTraversal struct {
87 // want is the DN of the node's that requested to be found.
88 want DN
89 // current is the path already taken to find the node, in the form of DN parts. It starts out as want.Parts() and
90 // progresses to become empty as the traversal continues.
91 current []string
92 // left is the path that's still needed to be taken in order to find the node, in the form of DN parts. It starts
93 // out empty and progresses to become wants.Parts() as the traversal continues.
94 left []string
95}
96
97// next adjusts the traversal's current/left slices to the next element of the traversal, returns the part that's now
98// being looked for (or "" if the traveral is done) and the full DN of the element that's being looked for.
99//
100// For example, a traversal of foo.bar.baz will cause .next() to return the following on each invocation:
101// - part: foo, full: foo
102// - part: bar, full: foo.bar
103// - part: baz, full: foo.bar.baz
104// - part: "", full: foo.bar.baz
105func (t *nodeTraversal) next() (part string, full DN) {
106 if len(t.left) == 0 {
107 return "", t.want
108 }
109 part = t.left[0]
110 t.current = append(t.current, part)
111 t.left = t.left[1:]
112 full = DN(strings.Join(t.current, "."))
113 return
114}
115
116// newTraversal returns a nodeTraversal fora a given wanted DN.
117func newTraversal(dn DN) (*nodeTraversal, error) {
118 parts, err := dn.Path()
119 if err != nil {
120 return nil, err
121 }
122 return &nodeTraversal{
123 want: dn,
124 left: parts,
125 }, nil
126}
127
128// execute the traversal in order to find the node. This can only be called once per traversal.
129// Nodes will be created within the tree until the target node is reached. Existing nodes will be reused.
130// This is effectively an idempotent way of accessing a node in the tree based on a traversal.
131func (t *nodeTraversal) execute(n *node) *node {
132 cur := n
133 for {
134 part, full := t.next()
135 if part == "" {
136 return cur
137 }
138
139 mu := &cur.mu
140 mu.Lock()
141 if _, ok := cur.children[part]; !ok {
142 cur.children[part] = newNode(n.tree, DN(full))
143 }
144 cur = cur.children[part]
145 mu.Unlock()
146 }
147}