logging/logging.go (241 lines of code) (raw):

// Copyright 1999-2020 Alibaba Group Holding Ltd. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package logging import ( "encoding/json" "errors" "fmt" "log" "os" "path/filepath" "runtime" "strings" "sync" "time" ) // Level represents the level of logging. type Level uint8 const ( DebugLevel Level = iota InfoLevel WarnLevel ErrorLevel ) const ( // RecordLogFileName represents the default file name of the record log. RecordLogFileName = "sentinel-record.log" GlobalCallerDepth = 4 defaultLogMsgBufferSize = 256 ) var ( DefaultDirName = filepath.Join("logs", "csp") ) var ( globalLogLevel = InfoLevel globalLogger = NewConsoleLogger() FrequentErrorOnce = &sync.Once{} ) // GetGlobalLoggerLevel gets the Sentinel log level func GetGlobalLoggerLevel() Level { return globalLogLevel } // ResetGlobalLoggerLevel sets the Sentinel log level // Note: this function is not thread-safe. func ResetGlobalLoggerLevel(l Level) { globalLogLevel = l } // GetGlobalLogger gets the Sentinel global logger func GetGlobalLogger() Logger { return globalLogger } // ResetGlobalLogger sets the Sentinel global logger // Note: this function is not thread-safe. func ResetGlobalLogger(log Logger) error { if log == nil { return errors.New("nil logger") } globalLogger = log return nil } func NewConsoleLogger() Logger { return &DefaultLogger{ log: log.New(os.Stdout, "", 0), } } // outputFile is the full path(absolute path) func NewSimpleFileLogger(filepath string) (Logger, error) { logFile, err := os.OpenFile(filepath, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0777) if err != nil { return nil, err } return &DefaultLogger{ log: log.New(logFile, "", 0), }, nil } type Logger interface { Debug(msg string, keysAndValues ...interface{}) DebugEnabled() bool // Info logs a non-error message with the given key/value pairs as context. // // The msg argument should be used to add some constant description to // the log line. The key/value pairs can then be used to add additional // variable information. The key/value pairs should alternate string // keys and arbitrary values. Info(msg string, keysAndValues ...interface{}) InfoEnabled() bool Warn(msg string, keysAndValues ...interface{}) WarnEnabled() bool Error(err error, msg string, keysAndValues ...interface{}) ErrorEnabled() bool } // sentinel general logger type DefaultLogger struct { log *log.Logger } func caller(depth int) (file string, line int) { _, file, line, ok := runtime.Caller(depth) if !ok { file = "???" line = 0 } // extract if osType := runtime.GOOS; osType == "windows" { file = strings.ReplaceAll(file, "\\", "/") } idx := strings.LastIndex(file, "/") file = file[idx+1:] return } // toSafeJSONString converts to valid JSON string, as the original string may contain '\\', '\n', '\r', '\t' and so on. func toSafeJSONString(s string) []byte { if data, err := json.Marshal(json.RawMessage(s)); err == nil { return data } else { return []byte("\"" + s + "\"") } } func AssembleMsg(depth int, logLevel, msg string, err error, keysAndValues ...interface{}) string { sb := strings.Builder{} sb.Grow(defaultLogMsgBufferSize) file, line := caller(depth) timeStr := time.Now().Format("2006-01-02 15:04:05.520") caller := fmt.Sprintf("%s:%d", file, line) sb.WriteString("{") sb.WriteByte('"') sb.WriteString("timestamp") sb.WriteByte('"') sb.WriteByte(':') sb.WriteByte('"') sb.WriteString(timeStr) sb.WriteByte('"') sb.WriteByte(',') sb.WriteByte('"') sb.WriteString("caller") sb.WriteByte('"') sb.WriteByte(':') sb.WriteByte('"') sb.WriteString(caller) sb.WriteByte('"') sb.WriteByte(',') sb.WriteByte('"') sb.WriteString("logLevel") sb.WriteByte('"') sb.WriteByte(':') sb.WriteByte('"') sb.WriteString(logLevel) sb.WriteByte('"') sb.WriteByte(',') sb.WriteByte('"') sb.WriteString("msg") sb.WriteByte('"') sb.WriteByte(':') data := toSafeJSONString(msg) sb.Write(data) kvLen := len(keysAndValues) if kvLen&1 != 0 { sb.WriteByte(',') sb.WriteByte('"') sb.WriteString("kvs") sb.WriteByte('"') sb.WriteByte(':') s := fmt.Sprintf("%+v", keysAndValues) data := toSafeJSONString(s) sb.Write(data) } else if kvLen != 0 { for i := 0; i < kvLen; { k := keysAndValues[i] v := keysAndValues[i+1] kStr, kIsStr := k.(string) if !kIsStr { kStr = fmt.Sprintf("%+v", k) } sb.WriteByte(',') data := toSafeJSONString(kStr) sb.Write(data) sb.WriteByte(':') switch v.(type) { case string: data := toSafeJSONString(v.(string)) sb.Write(data) case error: data := toSafeJSONString(v.(error).Error()) sb.Write(data) default: if vbs, err := json.Marshal(v); err != nil { s := fmt.Sprintf("%+v", v) data := toSafeJSONString(s) sb.Write(data) } else { sb.Write(vbs) } } i = i + 2 } } sb.WriteByte('}') if err != nil { sb.WriteString("\n") sb.WriteString(fmt.Sprintf("%+v", err)) } return sb.String() } func (l *DefaultLogger) Debug(msg string, keysAndValues ...interface{}) { if !l.DebugEnabled() { return } l.log.Print(AssembleMsg(GlobalCallerDepth, "DEBUG", msg, nil, keysAndValues...)) } func (l *DefaultLogger) DebugEnabled() bool { return DebugLevel >= globalLogLevel } func (l *DefaultLogger) Info(msg string, keysAndValues ...interface{}) { if !l.InfoEnabled() { return } l.log.Print(AssembleMsg(GlobalCallerDepth, "INFO", msg, nil, keysAndValues...)) } func (l *DefaultLogger) InfoEnabled() bool { return InfoLevel >= globalLogLevel } func (l *DefaultLogger) Warn(msg string, keysAndValues ...interface{}) { if !l.WarnEnabled() { return } l.log.Print(AssembleMsg(GlobalCallerDepth, "WARNING", msg, nil, keysAndValues...)) } func (l *DefaultLogger) WarnEnabled() bool { return WarnLevel >= globalLogLevel } func (l *DefaultLogger) Error(err error, msg string, keysAndValues ...interface{}) { if !l.ErrorEnabled() { return } l.log.Print(AssembleMsg(GlobalCallerDepth, "ERROR", msg, err, keysAndValues...)) } func (l *DefaultLogger) ErrorEnabled() bool { return ErrorLevel >= globalLogLevel } func Debug(msg string, keysAndValues ...interface{}) { globalLogger.Debug(msg, keysAndValues...) } func DebugEnabled() bool { return globalLogger.DebugEnabled() } func Info(msg string, keysAndValues ...interface{}) { globalLogger.Info(msg, keysAndValues...) } func InfoEnabled() bool { return globalLogger.InfoEnabled() } func Warn(msg string, keysAndValues ...interface{}) { globalLogger.Warn(msg, keysAndValues...) } func WarnEnabled() bool { return globalLogger.WarnEnabled() } func Error(err error, msg string, keysAndValues ...interface{}) { globalLogger.Error(err, msg, keysAndValues...) } func ErrorEnabled() bool { return globalLogger.ErrorEnabled() }