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