mirror of
https://github.com/gitpod-io/gitpod.git
synced 2025-12-08 17:36:30 +00:00
* [baseserver] Emit metrics for logs produced by level * fix * fix * fix * fix * Fix * Fix * fix * retest * fix
248 lines
6.2 KiB
Go
248 lines
6.2 KiB
Go
// Copyright (c) 2020 Gitpod GmbH. All rights reserved.
|
|
// Licensed under the GNU Affero General Public License (AGPL).
|
|
// See License.AGPL.txt in the project root for license information.
|
|
|
|
package log
|
|
|
|
import (
|
|
"bufio"
|
|
"bytes"
|
|
"encoding/json"
|
|
"fmt"
|
|
"os"
|
|
"path"
|
|
"reflect"
|
|
"runtime"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/sirupsen/logrus"
|
|
log "github.com/sirupsen/logrus"
|
|
|
|
"github.com/gitpod-io/gitpod/components/scrubber"
|
|
)
|
|
|
|
// Log is the application wide console logger
|
|
var Log = log.WithFields(log.Fields{})
|
|
|
|
func New() *log.Entry {
|
|
return Log.Dup()
|
|
}
|
|
|
|
// setup default log level for components without initial invocation of log.Init.
|
|
func init() {
|
|
logLevelFromEnv()
|
|
}
|
|
|
|
func logLevelFromEnv() {
|
|
level := os.Getenv("LOG_LEVEL")
|
|
if len(level) == 0 {
|
|
level = "info"
|
|
}
|
|
|
|
newLevel, err := logrus.ParseLevel(level)
|
|
if err != nil {
|
|
Log.WithError(err).Errorf("cannot change log level to '%v'", level)
|
|
return
|
|
}
|
|
|
|
Log.Logger.SetLevel(newLevel)
|
|
}
|
|
|
|
// Init initializes/configures the application-wide logger
|
|
func Init(service, version string, json, verbose bool) {
|
|
Log = log.WithFields(ServiceContext(service, version))
|
|
log.SetReportCaller(true)
|
|
|
|
log.AddHook(NewLogHook(DefaultMetrics))
|
|
|
|
if json {
|
|
Log.Logger.SetFormatter(newGcpFormatter(false))
|
|
} else {
|
|
Log.Logger.SetFormatter(&logrus.TextFormatter{
|
|
TimestampFormat: time.RFC3339Nano,
|
|
FullTimestamp: true,
|
|
})
|
|
}
|
|
|
|
// update default log level
|
|
logLevelFromEnv()
|
|
|
|
if verbose {
|
|
Log.Logger.SetLevel(log.DebugLevel)
|
|
}
|
|
}
|
|
|
|
// gcpFormatter formats errors according to GCP rules, see
|
|
type gcpFormatter struct {
|
|
log.JSONFormatter
|
|
skipScrub bool
|
|
}
|
|
|
|
func newGcpFormatter(skipScrub bool) *gcpFormatter {
|
|
return &gcpFormatter{
|
|
skipScrub: skipScrub,
|
|
JSONFormatter: log.JSONFormatter{
|
|
FieldMap: log.FieldMap{
|
|
log.FieldKeyMsg: "message",
|
|
},
|
|
CallerPrettyfier: func(f *runtime.Frame) (string, string) {
|
|
s := strings.Split(f.Function, ".")
|
|
funcName := s[len(s)-1]
|
|
return funcName, fmt.Sprintf("%s:%d", path.Base(f.File), f.Line)
|
|
},
|
|
TimestampFormat: time.RFC3339Nano,
|
|
},
|
|
}
|
|
}
|
|
|
|
func (f *gcpFormatter) Format(entry *log.Entry) ([]byte, error) {
|
|
hasError := false
|
|
for k, v := range entry.Data {
|
|
switch v := v.(type) {
|
|
case error:
|
|
// Otherwise errors are ignored by `encoding/json`
|
|
// https://github.com/sirupsen/logrus/issues/137
|
|
//
|
|
// Print errors verbosely to get stack traces where available
|
|
entry.Data[k] = fmt.Sprintf("%+v", v)
|
|
hasError = true
|
|
}
|
|
}
|
|
// map to gcp severity. See https://cloud.google.com/logging/docs/reference/v2/rest/v2/LogEntry#LogSeverity
|
|
var severity string = "INFO"
|
|
switch entry.Level {
|
|
case logrus.TraceLevel:
|
|
severity = "DEBUG"
|
|
case logrus.DebugLevel:
|
|
severity = "DEBUG"
|
|
case logrus.InfoLevel:
|
|
severity = "INFO"
|
|
case logrus.WarnLevel:
|
|
severity = "WARNING"
|
|
case logrus.ErrorLevel:
|
|
severity = "ERROR"
|
|
case logrus.FatalLevel:
|
|
severity = "CRITICAL"
|
|
case logrus.PanicLevel:
|
|
severity = "EMERGENCY"
|
|
}
|
|
entry.Data["severity"] = severity
|
|
if entry.Level <= log.WarnLevel && hasError {
|
|
entry.Data["@type"] = "type.googleapis.com/google.devtools.clouderrorreporting.v1beta1.ReportedErrorEvent"
|
|
}
|
|
|
|
if f.skipScrub {
|
|
return f.JSONFormatter.Format(entry)
|
|
}
|
|
|
|
for key, value := range entry.Data {
|
|
if key == "error" || key == "severity" || key == "message" || key == "time" || key == "serviceContext" || key == "context" {
|
|
continue
|
|
}
|
|
switch v := value.(type) {
|
|
case string:
|
|
entry.Data[key] = scrubber.Default.KeyValue(key, v)
|
|
case int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64, float32, float64, complex64, complex128:
|
|
// no-op
|
|
case bool:
|
|
// no-op
|
|
case time.Time, time.Duration:
|
|
// no-op
|
|
case scrubber.TrustedValue:
|
|
// no-op
|
|
default:
|
|
// handling of named primitive types
|
|
rv := reflect.ValueOf(value)
|
|
switch rv.Kind() {
|
|
case reflect.String:
|
|
entry.Data[key] = scrubber.Default.KeyValue(key, rv.String())
|
|
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64,
|
|
reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64,
|
|
reflect.Float32, reflect.Float64, reflect.Complex64, reflect.Complex128:
|
|
// no-op
|
|
case reflect.Bool:
|
|
// no-op
|
|
default:
|
|
// implement TrustedValue for custom types
|
|
// make sure to use the scrubber.Default to scrub sensitive data
|
|
entry.Data[key] = "[redacted:nested]"
|
|
}
|
|
}
|
|
}
|
|
return f.JSONFormatter.Format(entry)
|
|
}
|
|
|
|
// FromBuffer extracts the output generated by a command
|
|
// containing JSON output, parsing and writing underlying
|
|
// log data stream.
|
|
func FromBuffer(buf *bytes.Buffer, logger *logrus.Entry) {
|
|
scanner := bufio.NewScanner(buf)
|
|
for scanner.Scan() {
|
|
b := bytes.Trim(scanner.Bytes(), "\x00")
|
|
if len(b) == 0 {
|
|
continue
|
|
}
|
|
|
|
var entry jsonEntry
|
|
if err := json.Unmarshal(b, &entry); err != nil {
|
|
if _, ok := err.(*json.SyntaxError); !ok {
|
|
Log.Errorf("log.FromReader decoding JSON: %v", err)
|
|
}
|
|
|
|
continue
|
|
}
|
|
|
|
// common field name
|
|
message := entry.Message
|
|
if message == "" {
|
|
// msg is defined in runc
|
|
message = entry.Msg
|
|
}
|
|
|
|
// do not log empty messages
|
|
if message == "" {
|
|
continue
|
|
}
|
|
|
|
logEntry := logger.Dup()
|
|
logEntry.Level = entry.Level
|
|
logEntry.Message = message
|
|
if entry.Time != nil {
|
|
logEntry.Time = *entry.Time
|
|
} else {
|
|
logEntry.Time = time.Now()
|
|
}
|
|
|
|
// check the log of the entry is enable for the logger
|
|
if logEntry.Logger.IsLevelEnabled(entry.Level) {
|
|
b, err := logEntry.Bytes()
|
|
if err != nil {
|
|
Log.Errorf("Failed to write to custom log, %v", err)
|
|
}
|
|
if _, err := logEntry.Logger.Out.Write(b); err != nil {
|
|
Log.Errorf("Failed to write to custom log, %v", err)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
type jsonEntry struct {
|
|
Level logrus.Level `json:"level,omitempty"`
|
|
Message string `json:"message,omitempty"`
|
|
Msg string `json:"msg,omitempty"`
|
|
Time *time.Time `json:"time,omitempty"`
|
|
}
|
|
|
|
// TrustedValueWrap is a simple wrapper that treats the entire value as trusted, which are not processed by the scrubber.
|
|
// During JSON marshal, only the Value itself will be processed, without including Wrap.
|
|
type TrustedValueWrap struct {
|
|
Value any
|
|
}
|
|
|
|
func (TrustedValueWrap) IsTrustedValue() {}
|
|
|
|
func (t TrustedValueWrap) MarshalJSON() ([]byte, error) {
|
|
return json.Marshal(t.Value)
|
|
}
|