blob: 9fcaa4ca95b878aba3c1011fba2785b30a59f767 [file] [log] [blame]
Tim Windelschmidt6d33a432025-02-04 14:34:25 +01001// Copyright The Monogon Project Authors.
Serge Bazanski5faa2fc2020-09-07 14:09:30 +02002// SPDX-License-Identifier: Apache-2.0
Serge Bazanski5faa2fc2020-09-07 14:09:30 +02003
4package logtree
5
6import (
7 "fmt"
8 "strings"
9 "sync"
Serge Bazanskif68153c2020-10-26 13:54:37 +010010
Serge Bazanski3c5d0632024-09-12 10:49:12 +000011 "source.monogon.dev/go/logging"
Tim Windelschmidt9f21f532024-05-07 15:14:20 +020012 "source.monogon.dev/osbase/logbuffer"
Serge Bazanski5faa2fc2020-09-07 14:09:30 +020013)
14
Serge Bazanski216fe7b2021-05-21 18:36:16 +020015// LogTree is a tree-shaped logging system. For more information, see the package-
16// level documentation.
Serge Bazanski5faa2fc2020-09-07 14:09:30 +020017type LogTree struct {
18 // journal is the tree's journal, storing all log data and managing subscribers.
19 journal *journal
Serge Bazanski216fe7b2021-05-21 18:36:16 +020020 // root is the root node of the actual tree of the log tree. The nodes contain per-
21 // DN configuration options, notably the current verbosity level of that DN.
Serge Bazanski5faa2fc2020-09-07 14:09:30 +020022 root *node
23}
24
25func New() *LogTree {
26 lt := &LogTree{
27 journal: newJournal(),
28 }
29 lt.root = newNode(lt, "")
30 return lt
31}
32
Serge Bazanski216fe7b2021-05-21 18:36:16 +020033// node represents a given DN as a discrete 'logger'. It implements the
34// LeveledLogger interface for log publishing, entries from which it passes over to
35// the logtree's journal.
Serge Bazanski5faa2fc2020-09-07 14:09:30 +020036type node struct {
37 // dn is the DN which this node represents (or "" if this is the root node).
38 dn DN
39 // tree is the LogTree to which this node belongs.
40 tree *LogTree
Serge Bazanski216fe7b2021-05-21 18:36:16 +020041 // verbosity is the current verbosity level of this DN/node, affecting .V(n)
42 // LeveledLogger calls
Serge Bazanski3c5d0632024-09-12 10:49:12 +000043 verbosity logging.VerbosityLevel
Serge Bazanskif68153c2020-10-26 13:54:37 +010044 rawLineBuffer *logbuffer.LineBuffer
Serge Bazanski5faa2fc2020-09-07 14:09:30 +020045
46 // mu guards children.
47 mu sync.Mutex
Serge Bazanski216fe7b2021-05-21 18:36:16 +020048 // children is a map of DN-part to a children node in the logtree. A DN-part is a
49 // string representing a part of the DN between the deliming dots, as returned by
50 // DN.Path.
Serge Bazanski5faa2fc2020-09-07 14:09:30 +020051 children map[string]*node
52}
53
Serge Bazanski216fe7b2021-05-21 18:36:16 +020054// newNode returns a node at a given DN in the LogTree - but doesn't set up the
55// LogTree to insert it accordingly.
Serge Bazanski5faa2fc2020-09-07 14:09:30 +020056func newNode(tree *LogTree, dn DN) *node {
57 n := &node{
58 dn: dn,
59 tree: tree,
60 children: make(map[string]*node),
61 }
Serge Bazanskib0272182020-11-02 18:39:44 +010062 // TODO(q3k): make this limit configurable. If this happens, or the default (1024) gets changes, max chunk size
63 // calculations when serving the logs (eg. in NodeDebugService) must reflect this.
Serge Bazanskif68153c2020-10-26 13:54:37 +010064 n.rawLineBuffer = logbuffer.NewLineBuffer(1024, n.logRaw)
Serge Bazanski5faa2fc2020-09-07 14:09:30 +020065 return n
66}
67
Serge Bazanski216fe7b2021-05-21 18:36:16 +020068// nodeByDN returns the LogTree node corresponding to a given DN. If either the
69// node or some of its parents do not exist they will be created as needed.
Serge Bazanski5faa2fc2020-09-07 14:09:30 +020070func (l *LogTree) nodeByDN(dn DN) (*node, error) {
71 traversal, err := newTraversal(dn)
72 if err != nil {
73 return nil, fmt.Errorf("traversal failed: %w", err)
74 }
75 return traversal.execute(l.root), nil
76}
77
Serge Bazanski216fe7b2021-05-21 18:36:16 +020078// nodeTraversal represents a request to traverse the LogTree in search of a given
79// node by DN.
Serge Bazanski5faa2fc2020-09-07 14:09:30 +020080type nodeTraversal struct {
81 // want is the DN of the node's that requested to be found.
82 want DN
Serge Bazanski216fe7b2021-05-21 18:36:16 +020083 // current is the path already taken to find the node, in the form of DN parts. It
84 // starts out as want.Parts() and progresses to become empty as the traversal
85 // continues.
Serge Bazanski5faa2fc2020-09-07 14:09:30 +020086 current []string
Serge Bazanski216fe7b2021-05-21 18:36:16 +020087 // left is the path that's still needed to be taken in order to find the node, in
88 // the form of DN parts. It starts out empty and progresses to become wants.Parts()
89 // as the traversal continues.
Serge Bazanski5faa2fc2020-09-07 14:09:30 +020090 left []string
91}
92
Serge Bazanski216fe7b2021-05-21 18:36:16 +020093// next adjusts the traversal's current/left slices to the next element of the
94// traversal, returns the part that's now being looked for (or "" if the traveral
95// is done) and the full DN of the element that's being looked for.
Serge Bazanski5faa2fc2020-09-07 14:09:30 +020096//
Serge Bazanski216fe7b2021-05-21 18:36:16 +020097// For example, a traversal of foo.bar.baz will cause .next() to return the
98// following on each invocation:
Tim Windelschmidt5e460a92024-04-11 01:33:09 +020099// - part: foo, full: foo
100// - part: bar, full: foo.bar
101// - part: baz, full: foo.bar.baz
102// - part: "", full: foo.bar.baz
Serge Bazanski5faa2fc2020-09-07 14:09:30 +0200103func (t *nodeTraversal) next() (part string, full DN) {
104 if len(t.left) == 0 {
105 return "", t.want
106 }
107 part = t.left[0]
108 t.current = append(t.current, part)
109 t.left = t.left[1:]
110 full = DN(strings.Join(t.current, "."))
111 return
112}
113
114// newTraversal returns a nodeTraversal fora a given wanted DN.
115func newTraversal(dn DN) (*nodeTraversal, error) {
116 parts, err := dn.Path()
117 if err != nil {
118 return nil, err
119 }
120 return &nodeTraversal{
121 want: dn,
122 left: parts,
123 }, nil
124}
125
Serge Bazanski216fe7b2021-05-21 18:36:16 +0200126// execute the traversal in order to find the node. This can only be called once
127// per traversal. Nodes will be created within the tree until the target node is
128// reached. Existing nodes will be reused. This is effectively an idempotent way of
129// accessing a node in the tree based on a traversal.
Serge Bazanski5faa2fc2020-09-07 14:09:30 +0200130func (t *nodeTraversal) execute(n *node) *node {
131 cur := n
132 for {
133 part, full := t.next()
134 if part == "" {
135 return cur
136 }
137
138 mu := &cur.mu
139 mu.Lock()
140 if _, ok := cur.children[part]; !ok {
Tim Windelschmidt5e460a92024-04-11 01:33:09 +0200141 cur.children[part] = newNode(n.tree, full)
Serge Bazanski5faa2fc2020-09-07 14:09:30 +0200142 }
143 cur = cur.children[part]
144 mu.Unlock()
145 }
146}