forked from kata-containers/kata-containers
-
Notifications
You must be signed in to change notification settings - Fork 2
/
logentry.go
202 lines (162 loc) · 4.8 KB
/
logentry.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
//
// Copyright (c) 2017-2018 Intel Corporation
//
// SPDX-License-Identifier: Apache-2.0
//
package main
import (
"fmt"
"reflect"
"strings"
"time"
)
// TimeDelta is a time duration type used to record the difference between two
// timestamps.
type TimeDelta time.Duration
// NewTimeDelta create a new TimeDelta from the specified duration.
func NewTimeDelta(d time.Duration) TimeDelta {
return TimeDelta(d)
}
// String implements the Stringer interface to avoid the "clever" default for
// time.Duration which converts the value into a human-readable format (which
// varies on the value). We just want a fixed nanosecond format.
func (t TimeDelta) String() string {
return fmt.Sprintf("%d", t)
}
// MapSS is a hack for the xml package which is unable to marshal map types
// without help.
//
// See MapSS.MarshalXML()
type MapSS map[string]string
// Version of LogEntry contents (in semver.org format).
// XXX: Update whenever LogEntry changes!
const logEntryFormatVersion = "0.0.2"
// LogEntry is the main type used by the tool. It encapsulates a number of
// fields that all system components are expected to set, but also includes
// additional fields generated by this program.
//
// Notes:
//
// - An anonymous field is not used to distinguish between the two
// categories of fields because that extra layer becomes visible when the
// struct is converted to various formats (specifically CSV, text and YAML).
//
// - XXX: If you change this struct, update logEntryFormatVersion!
type LogEntry struct {
// Used to store additional (non-standard) fields
Data MapSS
Time time.Time
// Name of the file this entry belongs to
Filename string
Level string
Msg string
// System component type and name that generated the log entry
Source string
Name string
// Container ID. This is set for most, but not all log records.
//
// Excluded log records include:
//
// - runtime log entries where the specified CLI command does not
// operate on a container (or a single container).
//
// - proxy log entries which contain kernel boot output from the
// guest.
//
// - early startup agent log entries.
Container string
// Sandbox ID. This is set for most, but not all log records.
//
// Excluded log records include:
//
// - runtime log entries where the specified CLI command does not
// operate on a container (or a single container).
Sandbox string
// Line number in Filename this entry refers to
Line uint64
// Log entry number (1-indexed)
Count uint64
// difference between this record and the previous one
TimeDelta TimeDelta
Pid int
}
// Fields lists the names of the fields in a LogEntry.
func (le LogEntry) Fields() []string {
var fields []string
v := reflect.ValueOf(le)
for i := 0; i < v.NumField(); i++ {
field := v.Type().Field(i).Name
fields = append(fields, field)
}
return fields
}
// Check runs basic checks on the LogEntry to ensure it is valid.
func (le LogEntry) Check(ignoreMissingFields bool) error {
if le.Filename == "" {
return fmt.Errorf("missing filename: %+v", le)
}
if le.Filename != stdinFile {
if !strings.HasPrefix(le.Filename, "/") {
return fmt.Errorf("filename not absolute: %+v", le)
}
}
if le.Line == 0 {
return fmt.Errorf("missing line number: %+v", le)
}
if le.Time == (time.Time{}) {
return fmt.Errorf("missing timestamp: %+v", le)
}
if !ignoreMissingFields {
if le.Pid == 0 {
return fmt.Errorf("missing pid: %+v", le)
}
if le.Level == "" {
return fmt.Errorf("missing log level: %+v", le)
}
if le.Source == "" {
return fmt.Errorf("missing component source: %+v", le)
}
if le.Name == "" {
return fmt.Errorf("missing component name: %+v", le)
}
}
if le.Pid < 0 {
return fmt.Errorf("invalid pid: %+v", le)
}
// Note: le.Container and le.Sandbox cannot be checked since they are not
// present in all entries.
m := map[string]string{
"Level": le.Level,
"Source": le.Source,
"Name": le.Name,
}
for k, v := range m {
fields := strings.Fields(v)
if len(fields) > 1 {
return fmt.Errorf("field %q cannot be multi-word: %+v", k, le)
}
}
return nil
}
// LogEntries is a type that encapsulates a list of LogEntry elements.
// Strictly, this type could refer to the slice itself. However, some
// formatting packages (such as those for XML and TOML) require that the
// object to encode is a struct - not an array slice.
type LogEntries struct {
FormatVersion string
Entries []LogEntry
}
// Len is required by sort.Sort.
func (e LogEntries) Len() int {
return len(e.Entries)
}
// Swap is required by sort.Sort
func (e LogEntries) Swap(i, j int) {
entries := e.Entries
entries[i], entries[j] = entries[j], entries[i]
}
// Less is required by sort.Sort. Allows time-based sorting.
func (e LogEntries) Less(i, j int) bool {
entries := e.Entries
return entries[i].Time.Before(entries[j].Time)
}