blob: b6f8a06a8067415a619c53108b33266cadb768fd [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 Bazanski3c5d0632024-09-12 10:49:12 +000024 "source.monogon.dev/go/logging"
Tim Windelschmidt9f21f532024-05-07 15:14:20 +020025 "source.monogon.dev/osbase/logbuffer"
Serge Bazanski5faa2fc2020-09-07 14:09:30 +020026)
27
Serge Bazanski216fe7b2021-05-21 18:36:16 +020028// LogTree is a tree-shaped logging system. For more information, see the package-
29// level documentation.
Serge Bazanski5faa2fc2020-09-07 14:09:30 +020030type LogTree struct {
31 // journal is the tree's journal, storing all log data and managing subscribers.
32 journal *journal
Serge Bazanski216fe7b2021-05-21 18:36:16 +020033 // root is the root node of the actual tree of the log tree. The nodes contain per-
34 // DN configuration options, notably the current verbosity level of that DN.
Serge Bazanski5faa2fc2020-09-07 14:09:30 +020035 root *node
36}
37
38func New() *LogTree {
39 lt := &LogTree{
40 journal: newJournal(),
41 }
42 lt.root = newNode(lt, "")
43 return lt
44}
45
Serge Bazanski216fe7b2021-05-21 18:36:16 +020046// node represents a given DN as a discrete 'logger'. It implements the
47// LeveledLogger interface for log publishing, entries from which it passes over to
48// the logtree's journal.
Serge Bazanski5faa2fc2020-09-07 14:09:30 +020049type node struct {
50 // dn is the DN which this node represents (or "" if this is the root node).
51 dn DN
52 // tree is the LogTree to which this node belongs.
53 tree *LogTree
Serge Bazanski216fe7b2021-05-21 18:36:16 +020054 // verbosity is the current verbosity level of this DN/node, affecting .V(n)
55 // LeveledLogger calls
Serge Bazanski3c5d0632024-09-12 10:49:12 +000056 verbosity logging.VerbosityLevel
Serge Bazanskif68153c2020-10-26 13:54:37 +010057 rawLineBuffer *logbuffer.LineBuffer
Serge Bazanski5faa2fc2020-09-07 14:09:30 +020058
59 // mu guards children.
60 mu sync.Mutex
Serge Bazanski216fe7b2021-05-21 18:36:16 +020061 // children is a map of DN-part to a children node in the logtree. A DN-part is a
62 // string representing a part of the DN between the deliming dots, as returned by
63 // DN.Path.
Serge Bazanski5faa2fc2020-09-07 14:09:30 +020064 children map[string]*node
65}
66
Serge Bazanski216fe7b2021-05-21 18:36:16 +020067// newNode returns a node at a given DN in the LogTree - but doesn't set up the
68// LogTree to insert it accordingly.
Serge Bazanski5faa2fc2020-09-07 14:09:30 +020069func newNode(tree *LogTree, dn DN) *node {
70 n := &node{
71 dn: dn,
72 tree: tree,
73 children: make(map[string]*node),
74 }
Serge Bazanskib0272182020-11-02 18:39:44 +010075 // TODO(q3k): make this limit configurable. If this happens, or the default (1024) gets changes, max chunk size
76 // calculations when serving the logs (eg. in NodeDebugService) must reflect this.
Serge Bazanskif68153c2020-10-26 13:54:37 +010077 n.rawLineBuffer = logbuffer.NewLineBuffer(1024, n.logRaw)
Serge Bazanski5faa2fc2020-09-07 14:09:30 +020078 return n
79}
80
Serge Bazanski216fe7b2021-05-21 18:36:16 +020081// nodeByDN returns the LogTree node corresponding to a given DN. If either the
82// node or some of its parents do not exist they will be created as needed.
Serge Bazanski5faa2fc2020-09-07 14:09:30 +020083func (l *LogTree) nodeByDN(dn DN) (*node, error) {
84 traversal, err := newTraversal(dn)
85 if err != nil {
86 return nil, fmt.Errorf("traversal failed: %w", err)
87 }
88 return traversal.execute(l.root), nil
89}
90
Serge Bazanski216fe7b2021-05-21 18:36:16 +020091// nodeTraversal represents a request to traverse the LogTree in search of a given
92// node by DN.
Serge Bazanski5faa2fc2020-09-07 14:09:30 +020093type nodeTraversal struct {
94 // want is the DN of the node's that requested to be found.
95 want DN
Serge Bazanski216fe7b2021-05-21 18:36:16 +020096 // current is the path already taken to find the node, in the form of DN parts. It
97 // starts out as want.Parts() and progresses to become empty as the traversal
98 // continues.
Serge Bazanski5faa2fc2020-09-07 14:09:30 +020099 current []string
Serge Bazanski216fe7b2021-05-21 18:36:16 +0200100 // left is the path that's still needed to be taken in order to find the node, in
101 // the form of DN parts. It starts out empty and progresses to become wants.Parts()
102 // as the traversal continues.
Serge Bazanski5faa2fc2020-09-07 14:09:30 +0200103 left []string
104}
105
Serge Bazanski216fe7b2021-05-21 18:36:16 +0200106// next adjusts the traversal's current/left slices to the next element of the
107// traversal, returns the part that's now being looked for (or "" if the traveral
108// is done) and the full DN of the element that's being looked for.
Serge Bazanski5faa2fc2020-09-07 14:09:30 +0200109//
Serge Bazanski216fe7b2021-05-21 18:36:16 +0200110// For example, a traversal of foo.bar.baz will cause .next() to return the
111// following on each invocation:
Tim Windelschmidt5e460a92024-04-11 01:33:09 +0200112// - part: foo, full: foo
113// - part: bar, full: foo.bar
114// - part: baz, full: foo.bar.baz
115// - part: "", full: foo.bar.baz
Serge Bazanski5faa2fc2020-09-07 14:09:30 +0200116func (t *nodeTraversal) next() (part string, full DN) {
117 if len(t.left) == 0 {
118 return "", t.want
119 }
120 part = t.left[0]
121 t.current = append(t.current, part)
122 t.left = t.left[1:]
123 full = DN(strings.Join(t.current, "."))
124 return
125}
126
127// newTraversal returns a nodeTraversal fora a given wanted DN.
128func newTraversal(dn DN) (*nodeTraversal, error) {
129 parts, err := dn.Path()
130 if err != nil {
131 return nil, err
132 }
133 return &nodeTraversal{
134 want: dn,
135 left: parts,
136 }, nil
137}
138
Serge Bazanski216fe7b2021-05-21 18:36:16 +0200139// execute the traversal in order to find the node. This can only be called once
140// per traversal. Nodes will be created within the tree until the target node is
141// reached. Existing nodes will be reused. This is effectively an idempotent way of
142// accessing a node in the tree based on a traversal.
Serge Bazanski5faa2fc2020-09-07 14:09:30 +0200143func (t *nodeTraversal) execute(n *node) *node {
144 cur := n
145 for {
146 part, full := t.next()
147 if part == "" {
148 return cur
149 }
150
151 mu := &cur.mu
152 mu.Lock()
153 if _, ok := cur.children[part]; !ok {
Tim Windelschmidt5e460a92024-04-11 01:33:09 +0200154 cur.children[part] = newNode(n.tree, full)
Serge Bazanski5faa2fc2020-09-07 14:09:30 +0200155 }
156 cur = cur.children[part]
157 mu.Unlock()
158 }
159}