Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
gitpod-io
GitHub Repository: gitpod-io/gitpod
Path: blob/main/components/common-go/log/log.go
2498 views
1
// Copyright (c) 2020 Gitpod GmbH. All rights reserved.
2
// Licensed under the GNU Affero General Public License (AGPL).
3
// See License.AGPL.txt in the project root for license information.
4
5
package log
6
7
import (
8
"bufio"
9
"bytes"
10
"encoding/json"
11
"fmt"
12
"os"
13
"path"
14
"reflect"
15
"runtime"
16
"strings"
17
"time"
18
19
"github.com/sirupsen/logrus"
20
log "github.com/sirupsen/logrus"
21
22
"github.com/gitpod-io/gitpod/components/scrubber"
23
)
24
25
// Log is the application wide console logger
26
var Log = log.WithFields(log.Fields{})
27
28
func New() *log.Entry {
29
return Log.Dup()
30
}
31
32
// setup default log level for components without initial invocation of log.Init.
33
func init() {
34
logLevelFromEnv()
35
}
36
37
func logLevelFromEnv() {
38
level := os.Getenv("LOG_LEVEL")
39
if len(level) == 0 {
40
level = "info"
41
}
42
43
newLevel, err := logrus.ParseLevel(level)
44
if err != nil {
45
Log.WithError(err).Errorf("cannot change log level to '%v'", level)
46
return
47
}
48
49
Log.Logger.SetLevel(newLevel)
50
}
51
52
// Init initializes/configures the application-wide logger
53
func Init(service, version string, json, verbose bool) {
54
Log = log.WithFields(ServiceContext(service, version))
55
log.SetReportCaller(true)
56
57
log.AddHook(NewLogHook(DefaultMetrics))
58
59
if json {
60
Log.Logger.SetFormatter(newGcpFormatter(false))
61
} else {
62
Log.Logger.SetFormatter(&logrus.TextFormatter{
63
TimestampFormat: time.RFC3339Nano,
64
FullTimestamp: true,
65
})
66
}
67
68
// update default log level
69
logLevelFromEnv()
70
71
if verbose {
72
Log.Logger.SetLevel(log.DebugLevel)
73
}
74
}
75
76
// gcpFormatter formats errors according to GCP rules, see
77
type gcpFormatter struct {
78
log.JSONFormatter
79
skipScrub bool
80
}
81
82
func newGcpFormatter(skipScrub bool) *gcpFormatter {
83
return &gcpFormatter{
84
skipScrub: skipScrub,
85
JSONFormatter: log.JSONFormatter{
86
FieldMap: log.FieldMap{
87
log.FieldKeyMsg: "message",
88
},
89
CallerPrettyfier: func(f *runtime.Frame) (string, string) {
90
s := strings.Split(f.Function, ".")
91
funcName := s[len(s)-1]
92
return funcName, fmt.Sprintf("%s:%d", path.Base(f.File), f.Line)
93
},
94
TimestampFormat: time.RFC3339Nano,
95
},
96
}
97
}
98
99
func (f *gcpFormatter) Format(entry *log.Entry) ([]byte, error) {
100
hasError := false
101
for k, v := range entry.Data {
102
switch v := v.(type) {
103
case error:
104
// Otherwise errors are ignored by `encoding/json`
105
// https://github.com/sirupsen/logrus/issues/137
106
//
107
// Print errors verbosely to get stack traces where available
108
entry.Data[k] = fmt.Sprintf("%+v", v)
109
hasError = true
110
}
111
}
112
// map to gcp severity. See https://cloud.google.com/logging/docs/reference/v2/rest/v2/LogEntry#LogSeverity
113
var severity string = "INFO"
114
switch entry.Level {
115
case logrus.TraceLevel:
116
severity = "DEBUG"
117
case logrus.DebugLevel:
118
severity = "DEBUG"
119
case logrus.InfoLevel:
120
severity = "INFO"
121
case logrus.WarnLevel:
122
severity = "WARNING"
123
case logrus.ErrorLevel:
124
severity = "ERROR"
125
case logrus.FatalLevel:
126
severity = "CRITICAL"
127
case logrus.PanicLevel:
128
severity = "EMERGENCY"
129
}
130
entry.Data["severity"] = severity
131
if entry.Level <= log.WarnLevel && hasError {
132
entry.Data["@type"] = "type.googleapis.com/google.devtools.clouderrorreporting.v1beta1.ReportedErrorEvent"
133
}
134
135
if f.skipScrub {
136
return f.JSONFormatter.Format(entry)
137
}
138
139
for key, value := range entry.Data {
140
if key == "error" || key == "severity" || key == "message" || key == "time" || key == "serviceContext" || key == "context" {
141
continue
142
}
143
switch v := value.(type) {
144
case string:
145
entry.Data[key] = scrubber.Default.KeyValue(key, v)
146
case int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64, float32, float64, complex64, complex128:
147
// no-op
148
case bool:
149
// no-op
150
case time.Time, time.Duration:
151
// no-op
152
case scrubber.TrustedValue:
153
// no-op
154
default:
155
// handling of named primitive types
156
rv := reflect.ValueOf(value)
157
switch rv.Kind() {
158
case reflect.String:
159
entry.Data[key] = scrubber.Default.KeyValue(key, rv.String())
160
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64,
161
reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64,
162
reflect.Float32, reflect.Float64, reflect.Complex64, reflect.Complex128:
163
// no-op
164
case reflect.Bool:
165
// no-op
166
default:
167
// implement TrustedValue for custom types
168
// make sure to use the scrubber.Default to scrub sensitive data
169
entry.Data[key] = "[redacted:nested]"
170
}
171
}
172
}
173
return f.JSONFormatter.Format(entry)
174
}
175
176
// FromBuffer extracts the output generated by a command
177
// containing JSON output, parsing and writing underlying
178
// log data stream.
179
func FromBuffer(buf *bytes.Buffer, logger *logrus.Entry) {
180
scanner := bufio.NewScanner(buf)
181
for scanner.Scan() {
182
b := bytes.Trim(scanner.Bytes(), "\x00")
183
if len(b) == 0 {
184
continue
185
}
186
187
var entry jsonEntry
188
if err := json.Unmarshal(b, &entry); err != nil {
189
if _, ok := err.(*json.SyntaxError); !ok {
190
Log.Errorf("log.FromReader decoding JSON: %v", err)
191
}
192
193
continue
194
}
195
196
// common field name
197
message := entry.Message
198
if message == "" {
199
// msg is defined in runc
200
message = entry.Msg
201
}
202
203
// do not log empty messages
204
if message == "" {
205
continue
206
}
207
208
logEntry := logger.Dup()
209
logEntry.Level = entry.Level
210
logEntry.Message = message
211
if entry.Time != nil {
212
logEntry.Time = *entry.Time
213
} else {
214
logEntry.Time = time.Now()
215
}
216
217
// check the log of the entry is enable for the logger
218
if logEntry.Logger.IsLevelEnabled(entry.Level) {
219
b, err := logEntry.Bytes()
220
if err != nil {
221
Log.Errorf("Failed to write to custom log, %v", err)
222
}
223
if _, err := logEntry.Logger.Out.Write(b); err != nil {
224
Log.Errorf("Failed to write to custom log, %v", err)
225
}
226
}
227
}
228
}
229
230
type jsonEntry struct {
231
Level logrus.Level `json:"level,omitempty"`
232
Message string `json:"message,omitempty"`
233
Msg string `json:"msg,omitempty"`
234
Time *time.Time `json:"time,omitempty"`
235
}
236
237
// TrustedValueWrap is a simple wrapper that treats the entire value as trusted, which are not processed by the scrubber.
238
// During JSON marshal, only the Value itself will be processed, without including Wrap.
239
type TrustedValueWrap struct {
240
Value any
241
}
242
243
func (TrustedValueWrap) IsTrustedValue() {}
244
245
func (t TrustedValueWrap) MarshalJSON() ([]byte, error) {
246
return json.Marshal(t.Value)
247
}
248
249