blob: ed3ed7e0c0ed6dab04ac9f74f34b4e3be0dc228c [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"
Serge Bazanskib0272182020-11-02 18:39:44 +010021 "strconv"
22 "strings"
Serge Bazanski5faa2fc2020-09-07 14:09:30 +020023 "time"
Serge Bazanskib0272182020-11-02 18:39:44 +010024
Serge Bazanski31370b02021-01-07 16:31:14 +010025 apb "source.monogon.dev/metropolis/proto/api"
Serge Bazanski5faa2fc2020-09-07 14:09:30 +020026)
27
Serge Bazanski216fe7b2021-05-21 18:36:16 +020028// LeveledPayload is a log entry for leveled logs (as per leveled.go). It contains
29// the input to these calls (severity and message split into newline-delimited
30// messages) and additional metadata that would be usually seen in a text
Serge Bazanski12971d62020-11-17 12:12:58 +010031// representation of a leveled log entry.
Serge Bazanski1bfa0c22020-10-14 16:45:07 +020032type LeveledPayload struct {
Serge Bazanski216fe7b2021-05-21 18:36:16 +020033 // messages is the list of messages contained in this payload. This list is built
34 // from splitting up the given message from the user by newline.
Serge Bazanski12971d62020-11-17 12:12:58 +010035 messages []string
Serge Bazanski5faa2fc2020-09-07 14:09:30 +020036 // timestamp is the time at which this message was emitted.
37 timestamp time.Time
38 // severity is the leveled Severity at which this message was emitted.
39 severity Severity
40 // file is the filename of the caller that emitted this message.
41 file string
42 // line is the line number within the file of the caller that emitted this message.
43 line int
44}
45
Serge Bazanski216fe7b2021-05-21 18:36:16 +020046// String returns a canonical representation of this payload as a single string
47// prefixed with metadata. If the original message was logged with newlines, this
48// representation will also contain newlines, with each original message part
49// prefixed by the metadata. For an alternative call that will instead return a
50// canonical prefix and a list of lines in the message, see Strings().
Serge Bazanski1bfa0c22020-10-14 16:45:07 +020051func (p *LeveledPayload) String() string {
Serge Bazanski12971d62020-11-17 12:12:58 +010052 prefix, lines := p.Strings()
53 res := make([]string, len(p.messages))
54 for i, line := range lines {
55 res[i] = fmt.Sprintf("%s%s", prefix, line)
56 }
57 return strings.Join(res, "\n")
58}
Serge Bazanski5faa2fc2020-09-07 14:09:30 +020059
Serge Bazanski216fe7b2021-05-21 18:36:16 +020060// Strings returns the canonical representation of this payload split into a
61// prefix and all lines that were contained in the original message. This is
62// meant to be displayed to the user by showing the prefix before each line,
63// concatenated together - possibly in a table form with the prefixes all
64// unified with a rowspan- like mechanism.
Serge Bazanski12971d62020-11-17 12:12:58 +010065//
66// For example, this function can return:
67// prefix = "I1102 17:20:06.921395 foo.go:42] "
68// lines = []string{"current tags:", " - one", " - two"}
69//
70// With this data, the result should be presented to users this way in text form:
71// I1102 17:20:06.921395 foo.go:42] current tags:
72// I1102 17:20:06.921395 foo.go:42] - one
73// I1102 17:20:06.921395 foo.go:42] - two
74//
75// Or, in a table layout:
76// .-----------------------------------------------------------.
77// | I1102 17:20:06.921395 0 foo.go:42] : current tags: |
78// | :------------------|
79// | : - one |
80// | :------------------|
81// | : - two |
82// '-----------------------------------------------------------'
Serge Bazanski12971d62020-11-17 12:12:58 +010083func (p *LeveledPayload) Strings() (prefix string, lines []string) {
Serge Bazanski5faa2fc2020-09-07 14:09:30 +020084 _, month, day := p.timestamp.Date()
85 hour, minute, second := p.timestamp.Clock()
86 nsec := p.timestamp.Nanosecond() / 1000
87
Serge Bazanski12971d62020-11-17 12:12:58 +010088 // Same format as in glog, but without treadid.
89 // Lmmdd hh:mm:ss.uuuuuu file:line]
Serge Bazanski5faa2fc2020-09-07 14:09:30 +020090 // TODO(q3k): rewrite this to printf-less code.
Serge Bazanski12971d62020-11-17 12:12:58 +010091 prefix = fmt.Sprintf("%s%02d%02d %02d:%02d:%02d.%06d %s:%d] ", p.severity, month, day, hour, minute, second, nsec, p.file, p.line)
92
93 lines = p.messages
94 return
Serge Bazanski5faa2fc2020-09-07 14:09:30 +020095}
96
Serge Bazanski216fe7b2021-05-21 18:36:16 +020097// Message returns the inner message lines of this entry, ie. what was passed to
98// the actual logging method, but split by newlines.
Serge Bazanski12971d62020-11-17 12:12:58 +010099func (p *LeveledPayload) Messages() []string { return p.messages }
100
101func (p *LeveledPayload) MessagesJoined() string { return strings.Join(p.messages, "\n") }
Serge Bazanski5faa2fc2020-09-07 14:09:30 +0200102
103// Timestamp returns the time at which this entry was logged.
Serge Bazanski1bfa0c22020-10-14 16:45:07 +0200104func (p *LeveledPayload) Timestamp() time.Time { return p.timestamp }
Serge Bazanski5faa2fc2020-09-07 14:09:30 +0200105
Serge Bazanski216fe7b2021-05-21 18:36:16 +0200106// Location returns a string in the form of file_name:line_number that shows the
107// origin of the log entry in the program source.
Serge Bazanski1bfa0c22020-10-14 16:45:07 +0200108func (p *LeveledPayload) Location() string { return fmt.Sprintf("%s:%d", p.file, p.line) }
Serge Bazanski5faa2fc2020-09-07 14:09:30 +0200109
110// Severity returns the Severity with which this entry was logged.
Serge Bazanski1bfa0c22020-10-14 16:45:07 +0200111func (p *LeveledPayload) Severity() Severity { return p.severity }
Serge Bazanskib0272182020-11-02 18:39:44 +0100112
113// Proto converts a LeveledPayload to protobuf format.
114func (p *LeveledPayload) Proto() *apb.LogEntry_Leveled {
115 return &apb.LogEntry_Leveled{
Serge Bazanski12971d62020-11-17 12:12:58 +0100116 Lines: p.Messages(),
Serge Bazanskib0272182020-11-02 18:39:44 +0100117 Timestamp: p.Timestamp().UnixNano(),
118 Severity: p.Severity().ToProto(),
119 Location: p.Location(),
120 }
121}
122
123// LeveledPayloadFromProto parses a protobuf message into the internal format.
124func LeveledPayloadFromProto(p *apb.LogEntry_Leveled) (*LeveledPayload, error) {
125 severity, err := SeverityFromProto(p.Severity)
126 if err != nil {
127 return nil, fmt.Errorf("could not convert severity: %w", err)
128 }
129 parts := strings.Split(p.Location, ":")
130 if len(parts) != 2 {
131 return nil, fmt.Errorf("invalid location, must be two :-delimited parts, is %d parts", len(parts))
132 }
133 file := parts[0]
134 line, err := strconv.Atoi(parts[1])
135 if err != nil {
136 return nil, fmt.Errorf("invalid location line number: %w", err)
137 }
138 return &LeveledPayload{
Serge Bazanski12971d62020-11-17 12:12:58 +0100139 messages: p.Lines,
Serge Bazanskib0272182020-11-02 18:39:44 +0100140 timestamp: time.Unix(0, p.Timestamp),
141 severity: severity,
142 file: file,
143 line: line,
144 }, nil
145}