metropolis/pkg/logtree: allow logging external leveled payloads
This is in preparation for making the mechanism to ingest external
logging more generic (currently we have an ad-hoc solution for klog, but
we now also want to implement one for etcd).
Change-Id: I6e6f656e5d83ad22d67a81fbeb87c8d369796e18
Reviewed-on: https://review.monogon.dev/c/monogon/+/207
Reviewed-by: Leopold Schabel <leo@nexantic.com>
diff --git a/metropolis/pkg/logtree/leveled.go b/metropolis/pkg/logtree/leveled.go
index a4220f9..9590bc3 100644
--- a/metropolis/pkg/logtree/leveled.go
+++ b/metropolis/pkg/logtree/leveled.go
@@ -120,6 +120,17 @@
return false
}
+// Valid returns whether true if this severity is one of the known levels
+// (INFO, WARNING, ERROR or FATAL), false otherwise.
+func (s Severity) Valid() bool {
+ switch s {
+ case INFO, WARNING, ERROR, FATAL:
+ return true
+ default:
+ return false
+ }
+}
+
func SeverityFromProto(s apb.LeveledLogSeverity) (Severity, error) {
switch s {
case apb.LeveledLogSeverity_INFO:
diff --git a/metropolis/pkg/logtree/leveled_payload.go b/metropolis/pkg/logtree/leveled_payload.go
index ed3ed7e..a9ba56d 100644
--- a/metropolis/pkg/logtree/leveled_payload.go
+++ b/metropolis/pkg/logtree/leveled_payload.go
@@ -143,3 +143,55 @@
line: line,
}, nil
}
+
+// ExternalLeveledPayload is a LeveledPayload received from an external source,
+// eg. from parsing the logging output of third-party programs. It can be
+// converted into a LeveledPayload and inserted into a leveled logger, but will
+// be sanitized before that, ensuring that potentially buggy
+// emitters/converters do not end up polluting the leveled logger data.
+//
+// This type should be used only when inserting data from external systems, not
+// by code that just wishes to log things. In the future, data inserted this
+// way might be explicitly marked as tainted so operators can understand that
+// parts of this data might not give the same guarantees as the log entries
+// emitted by the native LeveledLogger API.
+type ExternalLeveledPayload struct {
+ // Log line. If any newlines are found, they will split the message into
+ // multiple messages within LeveledPayload. Empty messages are accepted
+ // verbatim.
+ Message string
+ // Timestamp when this payload was emitted according to its source. If not
+ // given, will default to the time of conversion to LeveledPayload.
+ Timestamp time.Time
+ // Log severity. If invalid or unset will default to INFO.
+ Severity Severity
+ // File name of originating code. Defaults to "unknown" if not set.
+ File string
+ // Line in File. Zero indicates the line is not known.
+ Line int
+}
+
+// sanitize the given ExternalLeveledPayload by creating a corresponding
+// LeveledPayload. The original object is unaltered.
+func (e *ExternalLeveledPayload) sanitize() *LeveledPayload {
+ l := &LeveledPayload{
+ messages: strings.Split(e.Message, "\n"),
+ timestamp: e.Timestamp,
+ severity: e.Severity,
+ file: e.File,
+ line: e.Line,
+ }
+ if l.timestamp.IsZero() {
+ l.timestamp = time.Now()
+ }
+ if !l.severity.Valid() {
+ l.severity = INFO
+ }
+ if l.file == "" {
+ l.file = "unknown"
+ }
+ if l.line < 0 {
+ l.line = 0
+ }
+ return l
+}
diff --git a/metropolis/pkg/logtree/logtree_publisher.go b/metropolis/pkg/logtree/logtree_publisher.go
index 6106b19..25dfc5a 100644
--- a/metropolis/pkg/logtree/logtree_publisher.go
+++ b/metropolis/pkg/logtree/logtree_publisher.go
@@ -81,6 +81,25 @@
n.tree.journal.notify(e)
}
+// LogExternalLeveled injects a ExternalLeveledPayload into a given
+// LeveledLogger. This should only be used by systems which translate external
+// data sources into leveled logging - see ExternelLeveledPayload for more
+// information.
+func LogExternalLeveled(l LeveledLogger, e *ExternalLeveledPayload) error {
+ n, ok := l.(*node)
+ if !ok {
+ return fmt.Errorf("the given LeveledLogger is not a logtree node")
+ }
+ p := e.sanitize()
+ entry := &entry{
+ origin: n.dn,
+ leveled: p,
+ }
+ n.tree.journal.append(entry)
+ n.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.