m/p/logtree: implement WithAddedStackDepth
This is a prerequisite to easily pass over trace-based events into
logtree. It allows a testing/Test.Helper()-like mechanism to skip some
stackframes within a call tree to the logger in order to log pertinent
log origins instead of a wrapper.
Change-Id: Ida9732f8505ff4a400e689045bea318a185f7983
Reviewed-on: https://review.monogon.dev/c/monogon/+/538
Reviewed-by: Leopold Schabel <leo@nexantic.com>
diff --git a/metropolis/pkg/logtree/klog.go b/metropolis/pkg/logtree/klog.go
index 07185ed..1c866f4 100644
--- a/metropolis/pkg/logtree/klog.go
+++ b/metropolis/pkg/logtree/klog.go
@@ -48,14 +48,14 @@
// the code handles the edge case of parsing a line from the end of a previous
// year at the beginning of the next).
func KLogParser(logger LeveledLogger) io.WriteCloser {
- n, ok := logger.(*node)
+ p, ok := logger.(*leveledPublisher)
if !ok {
// Fail fast, as this is a programming error.
- panic("Expected *node in LeveledLogger from supervisor")
+ panic("Expected *leveledPublisher in LeveledLogger from supervisor")
}
k := &klogParser{
- n: n,
+ publisher: p,
}
// klog seems to have no line length limit. Let's assume some sane sort of default.
k.buffer = logbuffer.NewLineBuffer(1024, k.consumeLine)
@@ -63,8 +63,8 @@
}
type klogParser struct {
- n *node
- buffer *logbuffer.LineBuffer
+ publisher *leveledPublisher
+ buffer *logbuffer.LineBuffer
}
func (k *klogParser) Write(p []byte) (n int, err error) {
@@ -84,17 +84,17 @@
if p == nil {
// We could instead emit that line as a raw log - however, this would lead to
// interleaving raw logging and leveled logging.
- k.n.Errorf("Invalid klog line: %s", l.Data)
+ k.publisher.Errorf("Invalid klog line: %s", l.Data)
}
// TODO(q3k): should this be exposed as an API on LeveledLogger? How much should
// we permit library users to 'fake' logs? This would also permit us to get rid
// of the type assertion in KLogParser().
e := &entry{
- origin: k.n.dn,
+ origin: k.publisher.node.dn,
leveled: p,
}
- k.n.tree.journal.append(e)
- k.n.tree.journal.notify(e)
+ k.publisher.node.tree.journal.append(e)
+ k.publisher.node.tree.journal.notify(e)
}
var (
diff --git a/metropolis/pkg/logtree/leveled.go b/metropolis/pkg/logtree/leveled.go
index 9590bc3..6e44ef4 100644
--- a/metropolis/pkg/logtree/leveled.go
+++ b/metropolis/pkg/logtree/leveled.go
@@ -62,6 +62,19 @@
// given verbosity level was active at that time, and as such should not be a long-
// lived object in programs. This construct is further refered to as 'V-logs'.
V(level VerbosityLevel) VerboseLeveledLogger
+
+ // WithAddedStackDepth returns the same LeveledLogger, but adjusted with an
+ // additional 'extra stack depth' which will be used to skip a given number of
+ // stack/call frames when determining the location where the error originated.
+ // For example, WithStackDepth(1) will return a logger that will skip one
+ // stack/call frame. Then, with function foo() calling function helper() which
+ // in turns call l.Infof(), the log line will be emitted with the call site of
+ // helper() within foo(), instead of the default behaviour of logging the
+ // call site of Infof() within helper().
+ //
+ // This is useful for functions which somehow wrap loggers in helper functions,
+ // for example to expose a slightly different API.
+ WithAddedStackDepth(depth int) LeveledLogger
}
// VerbosityLevel is a verbosity level defined for V-logs. This can be changed
diff --git a/metropolis/pkg/logtree/logtree_publisher.go b/metropolis/pkg/logtree/logtree_publisher.go
index 25dfc5a..0b945e3 100644
--- a/metropolis/pkg/logtree/logtree_publisher.go
+++ b/metropolis/pkg/logtree/logtree_publisher.go
@@ -26,10 +26,22 @@
"source.monogon.dev/metropolis/pkg/logbuffer"
)
+type leveledPublisher struct {
+ node *node
+ depth int
+}
+
// LeveledFor returns a LeveledLogger publishing interface for a given DN. An error
// may be returned if the DN is malformed.
func (l *LogTree) LeveledFor(dn DN) (LeveledLogger, error) {
- return l.nodeByDN(dn)
+ node, err := l.nodeByDN(dn)
+ if err != nil {
+ return nil, err
+ }
+ return &leveledPublisher{
+ node: node,
+ depth: 0,
+ }, nil
}
func (l *LogTree) RawFor(dn DN) (io.Writer, error) {
@@ -86,24 +98,24 @@
// data sources into leveled logging - see ExternelLeveledPayload for more
// information.
func LogExternalLeveled(l LeveledLogger, e *ExternalLeveledPayload) error {
- n, ok := l.(*node)
+ publisher, ok := l.(*leveledPublisher)
if !ok {
- return fmt.Errorf("the given LeveledLogger is not a logtree node")
+ return fmt.Errorf("the given LeveledLogger is not a *leveledPublisher")
}
p := e.sanitize()
entry := &entry{
- origin: n.dn,
+ origin: publisher.node.dn,
leveled: p,
}
- n.tree.journal.append(entry)
- n.tree.journal.notify(entry)
+ publisher.node.tree.journal.append(entry)
+ publisher.node.tree.journal.notify(entry)
return nil
}
// log builds a LeveledPayload and entry for a given message, including all related
// metadata. It will create a new entry append it to the journal, and notify all
// pertinent subscribers.
-func (n *node) logLeveled(depth int, severity Severity, msg string) {
+func (l *leveledPublisher) logLeveled(depth int, severity Severity, msg string) {
_, file, line, ok := runtime.Caller(2 + depth)
if !ok {
file = "???"
@@ -126,58 +138,65 @@
line: line,
}
e := &entry{
- origin: n.dn,
+ origin: l.node.dn,
leveled: p,
}
- n.tree.journal.append(e)
- n.tree.journal.notify(e)
+ l.node.tree.journal.append(e)
+ l.node.tree.journal.notify(e)
}
// Info implements the LeveledLogger interface.
-func (n *node) Info(args ...interface{}) {
- n.logLeveled(0, INFO, fmt.Sprint(args...))
+func (l *leveledPublisher) Info(args ...interface{}) {
+ l.logLeveled(l.depth, INFO, fmt.Sprint(args...))
}
// Infof implements the LeveledLogger interface.
-func (n *node) Infof(format string, args ...interface{}) {
- n.logLeveled(0, INFO, fmt.Sprintf(format, args...))
+func (l *leveledPublisher) Infof(format string, args ...interface{}) {
+ l.logLeveled(l.depth, INFO, fmt.Sprintf(format, args...))
}
// Warning implements the LeveledLogger interface.
-func (n *node) Warning(args ...interface{}) {
- n.logLeveled(0, WARNING, fmt.Sprint(args...))
+func (l *leveledPublisher) Warning(args ...interface{}) {
+ l.logLeveled(l.depth, WARNING, fmt.Sprint(args...))
}
// Warningf implements the LeveledLogger interface.
-func (n *node) Warningf(format string, args ...interface{}) {
- n.logLeveled(0, WARNING, fmt.Sprintf(format, args...))
+func (l *leveledPublisher) Warningf(format string, args ...interface{}) {
+ l.logLeveled(l.depth, WARNING, fmt.Sprintf(format, args...))
}
// Error implements the LeveledLogger interface.
-func (n *node) Error(args ...interface{}) {
- n.logLeveled(0, ERROR, fmt.Sprint(args...))
+func (l *leveledPublisher) Error(args ...interface{}) {
+ l.logLeveled(l.depth, ERROR, fmt.Sprint(args...))
}
// Errorf implements the LeveledLogger interface.
-func (n *node) Errorf(format string, args ...interface{}) {
- n.logLeveled(0, ERROR, fmt.Sprintf(format, args...))
+func (l *leveledPublisher) Errorf(format string, args ...interface{}) {
+ l.logLeveled(l.depth, ERROR, fmt.Sprintf(format, args...))
}
// Fatal implements the LeveledLogger interface.
-func (n *node) Fatal(args ...interface{}) {
- n.logLeveled(0, FATAL, fmt.Sprint(args...))
+func (l *leveledPublisher) Fatal(args ...interface{}) {
+ l.logLeveled(l.depth, FATAL, fmt.Sprint(args...))
}
// Fatalf implements the LeveledLogger interface.
-func (n *node) Fatalf(format string, args ...interface{}) {
- n.logLeveled(0, FATAL, fmt.Sprintf(format, args...))
+func (l *leveledPublisher) Fatalf(format string, args ...interface{}) {
+ l.logLeveled(l.depth, FATAL, fmt.Sprintf(format, args...))
+}
+
+// WithAddedStackDepth impleemnts the LeveledLogger interface.
+func (l *leveledPublisher) WithAddedStackDepth(depth int) LeveledLogger {
+ l2 := *l
+ l2.depth += depth
+ return &l2
}
// V implements the LeveledLogger interface.
-func (n *node) V(v VerbosityLevel) VerboseLeveledLogger {
+func (l *leveledPublisher) V(v VerbosityLevel) VerboseLeveledLogger {
return &verbose{
- node: n,
- enabled: n.verbosity >= v,
+ publisher: l,
+ enabled: l.node.verbosity >= v,
}
}
@@ -186,8 +205,9 @@
// VerboseLeveledLoggers must be short lived, as a changed in verbosity will not
// affect all already existing VerboseLeveledLoggers.
type verbose struct {
- node *node
- enabled bool
+ publisher *leveledPublisher
+ node *node
+ enabled bool
}
func (v *verbose) Enabled() bool {
@@ -198,12 +218,12 @@
if !v.enabled {
return
}
- v.node.logLeveled(0, INFO, fmt.Sprint(args...))
+ v.publisher.logLeveled(v.publisher.depth, INFO, fmt.Sprint(args...))
}
func (v *verbose) Infof(format string, args ...interface{}) {
if !v.enabled {
return
}
- v.node.logLeveled(0, INFO, fmt.Sprintf(format, args...))
+ v.publisher.logLeveled(v.publisher.depth, INFO, fmt.Sprintf(format, args...))
}
diff --git a/metropolis/pkg/logtree/logtree_test.go b/metropolis/pkg/logtree/logtree_test.go
index b900201..315dbc3 100644
--- a/metropolis/pkg/logtree/logtree_test.go
+++ b/metropolis/pkg/logtree/logtree_test.go
@@ -209,3 +209,31 @@
t.Fatalf("wanted entry %q, got %q", want, got)
}
}
+
+func TestAddedStackDepth(t *testing.T) {
+ tree := New()
+ helper := func(msg string) {
+ tree.MustLeveledFor("main").WithAddedStackDepth(1).Infof("oh no: %s", msg)
+ }
+
+ // The next three lines are tested to be next to each other.
+ helper("it failed")
+ tree.MustLeveledFor("main").Infof("something else")
+
+ reader, err := tree.Read("main", WithBacklog(BacklogAllAvailable))
+ if err != nil {
+ t.Fatalf("Read: %v", err)
+ }
+ if want, got := 2, len(reader.Backlog); want != got {
+ t.Fatalf("wanted %d entries, got %d", want, got)
+ }
+ if want, got := "oh no: it failed", reader.Backlog[0].Leveled.MessagesJoined(); want != got {
+ t.Errorf("wanted entry %q, got %q", want, got)
+ }
+ if want, got := "something else", reader.Backlog[1].Leveled.MessagesJoined(); want != got {
+ t.Errorf("wanted entry %q, got %q", want, got)
+ }
+ if first, second := reader.Backlog[0].Leveled.line, reader.Backlog[1].Leveled.line; first+1 != second {
+ t.Errorf("first entry at %d, second at %d, wanted one after the other", first, second)
+ }
+}