diff --git a/go.mod b/go.mod index 8d430a9..c8b49bd 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,6 @@ module github.com/opensergo/opensergo-control-plane go 1.14 require ( - github.com/alibaba/sentinel-golang v1.0.3 github.com/envoyproxy/protoc-gen-validate v0.1.0 github.com/go-logr/logr v0.4.0 github.com/json-iterator/go v1.1.12 // indirect diff --git a/pkg/common/logging/README.md b/pkg/common/logging/README.md new file mode 100644 index 0000000..e69de29 diff --git a/pkg/common/logging/doc.go b/pkg/common/logging/doc.go new file mode 100644 index 0000000..4060706 --- /dev/null +++ b/pkg/common/logging/doc.go @@ -0,0 +1,16 @@ +// Copyright 2022, OpenSergo Authors +// +// 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. +package logging diff --git a/pkg/common/logging/logger.go b/pkg/common/logging/logger.go new file mode 100644 index 0000000..14c04aa --- /dev/null +++ b/pkg/common/logging/logger.go @@ -0,0 +1,194 @@ +// Copyright 2022, OpenSergo Authors +// +// 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 ( + "github.com/pkg/errors" + "reflect" +) + +type Logger interface { + + // Print logs message no format as what the msg presents. + Print(msg string) + + // DebugEnabled judge is the DebugLevel enabled + DebugEnabled() bool + // Debug 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. + Debug(msg string, keysAndValues ...interface{}) + + // InfoEnabled judge is the InfoLevel enabled + InfoEnabled() 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{}) + + // WarnEnabled judge is the WarnLevel enabled + WarnEnabled() bool + // Warn 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. + Warn(msg string, keysAndValues ...interface{}) + + // ErrorEnabled judge is the ErrorLevel enabled + ErrorEnabled() bool + // Error logs an error message with error and 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. + Error(err error, msg string, keysAndValues ...interface{}) +} + +var ( + loggerSlice = make([]Logger, 0) + + consoleLogger Logger +) + +func Print(msg string) { + doLog("Print", nil, msg) +} + +func Debug(msg string, keysAndValues ...interface{}) { + doLog("Debug", nil, msg, keysAndValues...) +} + +func DebugWithCallerDepth(logger Logger, logFormat LogFormat, logCallerDepth int, msg string, keysAndValues ...interface{}) { + if !logger.DebugEnabled() { + return + } + logger.Print(AssembleMsg(logFormat, logCallerDepth, "DEBUG", msg, nil, false, keysAndValues...)) +} + +func Info(msg string, keysAndValues ...interface{}) { + doLog("Info", nil, msg, keysAndValues...) +} + +func InfoWithCallerDepth(logger Logger, logFormat LogFormat, logCallerDepth int, msg string, keysAndValues ...interface{}) { + if !logger.InfoEnabled() { + return + } + logger.Print(AssembleMsg(logFormat, logCallerDepth, "INFO", msg, nil, false, keysAndValues...)) +} + +func Warn(msg string, keysAndValues ...interface{}) { + doLog("Warn", nil, msg, keysAndValues...) +} + +func WarnWithCallerDepth(logger Logger, logFormat LogFormat, logCallerDepth int, msg string, keysAndValues ...interface{}) { + if !logger.WarnEnabled() { + return + } + + logger.Print(AssembleMsg(logFormat, logCallerDepth, "WARN", msg, nil, false, keysAndValues...)) +} + +func Error(err error, msg string, keysAndValues ...interface{}) { + doLog("Error", err, msg, keysAndValues...) +} + +func ErrorWithCallerDepth(logger Logger, logFormat LogFormat, logCallerDepth int, err error, errorWithStack bool, msg string, keysAndValues ...interface{}) { + if !logger.ErrorEnabled() { + return + } + logger.Print(AssembleMsg(logFormat, logCallerDepth, "ERROR", msg, err, errorWithStack, keysAndValues...)) +} + +// AppendLoggerSlice add the Logger into loggerSlice +func AppendLoggerSlice(loggerAppend Logger) { + loggerSlice = append(loggerSlice, loggerAppend) +} + +// ClearLoggerSlice clear the Logger into loggerSlice +func ClearLoggerSlice() { + loggerSlice = make([]Logger, 0) +} + +// SetConsoleLogger set the consoleLogger to print int stdout +func SetConsoleLogger(logger Logger) { + consoleLogger = logger +} + +// doLog do log +// funcNameFromInterface funcName in Logger +// err +// msg +// keysAndValues +func doLog(funcNameFromInterface string, err error, msg string, keysAndValues ...interface{}) { + if consoleLogger == nil && len(loggerSlice) == 0 { + NewDefaultConsoleLogger(InfoLevel) + } + + if consoleLogger != nil { + invokeLogger(consoleLogger, funcNameFromInterface, err, msg, keysAndValues...) + } + + if len(loggerSlice) > 0 { + for _, logger := range loggerSlice { + invokeLogger(logger, funcNameFromInterface, err, msg, keysAndValues...) + } + } +} + +// invokeLogger do log actually by invoke function of Logger +// logger Logger to print +// funcNameFromInterface funcName in Logger +// err +// msg +// keysAndValues +func invokeLogger(logger Logger, funcNameFromInterface string, err error, msg string, keysAndValues ...interface{}) { + method, ok := reflect.TypeOf(logger).MethodByName(funcNameFromInterface) + if !ok { + assembleMsg := AssembleMsg(SeparateFormat, 4, "WARN", "no function named '"+funcNameFromInterface+"' was found in interface 'opensergo-go/pkg/logging/Logger'", nil, false) + logger.Print(assembleMsg) + return + } + + keysAndValuesLen := len(keysAndValues) + params := make([]reflect.Value, 0) + params = append(params, reflect.ValueOf(logger)) + if "Error" == funcNameFromInterface { + if err == nil { + err = errors.New("") + } + params = append(params, reflect.ValueOf(err)) + } + params = append(params, reflect.ValueOf(msg)) + + if keysAndValuesLen != 0 { + if keysAndValuesLen == 1 && keysAndValues[0] == nil { + + } else { + for _, keyOrValue := range keysAndValues { + params = append(params, reflect.ValueOf(keyOrValue)) + } + } + } + method.Func.Call(params) +} diff --git a/pkg/common/logging/logger_assembler.go b/pkg/common/logging/logger_assembler.go new file mode 100644 index 0000000..fcb296c --- /dev/null +++ b/pkg/common/logging/logger_assembler.go @@ -0,0 +1,329 @@ +// Copyright 2022, OpenSergo Authors +// +// 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" + "fmt" + "runtime" + "strings" + "time" +) + +const ( + defaultLogMsgBufferSize = 256 +) + +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 + "\"") + } +} + +// AssembleMsg Assemble log-msg +// +// if logFormat is not matched, return JsonFormat as default +func AssembleMsg(logFormat LogFormat, callerDepth int, logLevel, msg string, err error, errWithStack bool, keysAndValues ...interface{}) string { + switch logFormat { + case SeparateFormat: + return AssembleMsgSeparateFormat(callerDepth, logLevel, msg, err, errWithStack, keysAndValues...) + case JsonFormat: + return AssembleMsgJsonFormat(callerDepth, logLevel, msg, err, errWithStack, keysAndValues...) + default: + return AssembleMsgJsonFormat(callerDepth, logLevel, msg, err, errWithStack, keysAndValues...) + } +} + +// AssembleMsgJsonFormat Assemble log-msg as json-format +// +// debug/info/warn: +// {"logLevel":"INFO","timestamp":"2006-01-02 15:04:05.000","caller":"opensergo_client.go:74","msg":"openSergoClient is starting..."} +// +// error: +// {"logLevel":"ERROR","timestamp":"2006-01-02 15:04:05.000","caller":"opensergo_client.go:83","msg":"can not connect.","error":"rpc error: code = Unavailable desc = connection error: desc = \"transport: Error while dialing dial tcp 33.1.33.1:10246: connect: connection refused\""} +// [ ERROR CAUSES WITH STACK ][ rpc error: code = Unavailable desc = connection error: desc = "transport: Error while dialing dial tcp 33.1.33.1:10246: connect: connection refused" +// github.com/opensergo/opensergo-go/pkg/client.handleReceive.func1 +// +// .../opensergo-go-sdk/pkg/client/opensergo_client.go:82 ][ ERROR CAUSES WITH STACK END ] +func AssembleMsgJsonFormat(callerDepth int, logLevel, msg string, err error, errWithStack bool, keysAndValues ...interface{}) string { + sb := strings.Builder{} + sb.Grow(defaultLogMsgBufferSize) + + file, line := caller(callerDepth) + timeStr := time.Now().Format("2006-01-02 15:04:05.000") + callerStr := fmt.Sprintf("%s:%d", file, line) + + sbLevel := strings.Builder{} + sbLevel.Grow(defaultLogMsgBufferSize) + sbLevel.WriteByte('"') + sbLevel.WriteString("logLevel") + sbLevel.WriteByte('"') + sbLevel.WriteByte(':') + sbLevel.WriteByte('"') + sbLevel.WriteString(logLevel) + sbLevel.WriteByte('"') + + sbTimestamp := strings.Builder{} + sbTimestamp.Grow(defaultLogMsgBufferSize) + sbTimestamp.WriteByte(',') + sbTimestamp.WriteByte('"') + sbTimestamp.WriteString("timestamp") + sbTimestamp.WriteByte('"') + sbTimestamp.WriteByte(':') + sbTimestamp.WriteByte('"') + sbTimestamp.WriteString(fmt.Sprintf("%-23s", timeStr)) + sbTimestamp.WriteByte('"') + + sbCaller := strings.Builder{} + sbCaller.Grow(defaultLogMsgBufferSize) + sbCaller.WriteByte(',') + sbCaller.WriteByte('"') + sbCaller.WriteString("caller") + sbCaller.WriteByte('"') + sbCaller.WriteByte(':') + sbCaller.WriteByte('"') + sbCaller.WriteString(callerStr) + sbCaller.WriteByte('"') + + sbMsgJson := strings.Builder{} + sbMsgJson.Grow(defaultLogMsgBufferSize * 2) + sbMsgJson.WriteByte(',') + sbMsgJson.WriteByte('"') + sbMsgJson.WriteString("msg") + sbMsgJson.WriteByte('"') + sbMsgJson.WriteByte(':') + sbMsgJson.WriteByte('"') + sbMsgJson.WriteString(msg) + sbMsgJson.WriteByte('"') + + sbKvs := strings.Builder{} + sbKvs.Grow(defaultLogMsgBufferSize) + kvLen := len(keysAndValues) + if kvLen&1 != 0 { + sbKvs.WriteByte(',') + sbKvs.WriteByte('"') + sbKvs.WriteString("kvs") + sbKvs.WriteByte('"') + sbKvs.WriteByte(':') + sbKvs.Write(toSafeJSONString(fmt.Sprintf("%+v", keysAndValues))) + } 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) + } + sbKvs.WriteByte(',') + sbKvs.Write(toSafeJSONString(kStr)) + sbKvs.WriteByte(':') + switch v.(type) { + case string: + sbKvs.Write(toSafeJSONString(v.(string))) + case error: + sbKvs.Write(toSafeJSONString(v.(error).Error())) + default: + if vbs, err := json.Marshal(v); err != nil { + sbKvs.Write(toSafeJSONString(fmt.Sprintf("%+v", v))) + } else { + sbKvs.Write(vbs) + } + } + i = i + 2 + } + } + sbMsgJson.WriteString(sbKvs.String()) + + sbError := strings.Builder{} + sbError.Grow(defaultLogMsgBufferSize) + if err != nil && err.Error() != "" { + sbMsgJson.WriteByte(',') + sbMsgJson.WriteByte('"') + sbMsgJson.WriteString("error") + sbMsgJson.WriteByte('"') + sbMsgJson.WriteByte(':') + sbMsgJson.WriteByte('"') + sbMsgJson.WriteString(strings.ReplaceAll(err.Error(), "\"", "\\\"")) + sbMsgJson.WriteByte('"') + + if errWithStack { + sbError.WriteString("[ ") + sbError.WriteString("ERROR CAUSES WITH STACK") + sbError.WriteString(" ]") + sbError.WriteString("[ ") + sbError.WriteString(fmt.Sprintf("%+v", err)) + sbError.WriteString(" ]") + sbError.WriteString("[ ") + sbError.WriteString("ERROR CAUSES WITH STACK END") + sbError.WriteString(" ]") + } + } + + sbLog := strings.Builder{} + sbLog.Grow(sbLevel.Len() + sbTimestamp.Len() + sbCaller.Len() + sbMsgJson.Len() + sbError.Len() + 4) + sbLog.WriteString("{") + sbLog.WriteString(sbLevel.String()) + sbLog.WriteString(sbTimestamp.String()) + sbLog.WriteString(sbCaller.String()) + sbLog.WriteString(sbMsgJson.String()) + sbLog.WriteString("}") + + if sbError.Len() > 0 { + sbLog.WriteString("\n") + sbLog.WriteString(sbError.String()) + } + + return sbLog.String() +} + +// AssembleMsgSeparateFormat Assemble log-msg as separate-format +// +// pattern: +// level | timestamp | callerFile:line | logContentJson | errorInfo +// +// debug/info/warn: +// INFO | 2006-01-02 15:04:05.000 | main.go:30 | {"msg":"connected.", kvs:{}} +// +// error: +// ERROR | 2006-01-02 15:04:05.000 | main.go:30 | {"msg":"connected.", kvs:{}} | rpc error: code = Unavailable desc = connection error: desc = "transport: Error while dialing dial tcp 33.1.33.1:10246: i/o timeout" +// [ ERROR CAUSES WITH STACK ][ rpc error: code = Unavailable desc = connection error: desc = "transport: Error while dialing dial tcp 33.1.33.1:10246: connect: connection refused" +// github.com/opensergo/opensergo-go/pkg/client.handleReceive.func1 +// +// .../opensergo-go-sdk/pkg/client/opensergo_client.go:82 ][ ERROR CAUSES WITH STACK END ] +func AssembleMsgSeparateFormat(callerDepth int, logLevel, msg string, err error, errWithStack bool, keysAndValues ...interface{}) string { + + file, line := caller(callerDepth) + timeStr := time.Now().Format("2006-01-02 15:04:05.000") + callerStr := fmt.Sprintf("%s:%d", file, line) + + sbLevel := strings.Builder{} + sbLevel.Grow(defaultLogMsgBufferSize) + sbLevel.WriteString(fmt.Sprintf("%-5s", logLevel)) + + sbTimestamp := strings.Builder{} + sbTimestamp.Grow(defaultLogMsgBufferSize) + sbTimestamp.WriteString(" | ") + sbTimestamp.WriteString(fmt.Sprintf("%-23s", timeStr)) + + sbCaller := strings.Builder{} + sbCaller.Grow(defaultLogMsgBufferSize) + sbCaller.WriteString(" | ") + sbCaller.WriteString(callerStr) + + sbLogJson := strings.Builder{} + sbLogJson.Grow(defaultLogMsgBufferSize * 2) + sbLogJson.WriteString(" | ") + sbLogJson.WriteString("{") + sbLogJson.WriteByte('"') + sbLogJson.WriteString("msg") + sbLogJson.WriteByte('"') + sbLogJson.WriteByte(':') + sbLogJson.WriteByte('"') + sbLogJson.WriteString(msg) + sbLogJson.WriteByte('"') + + sbKvs := strings.Builder{} + sbKvs.Grow(defaultLogMsgBufferSize) + kvLen := len(keysAndValues) + if kvLen&1 != 0 { + sbKvs.WriteByte(',') + sbKvs.WriteByte('"') + sbKvs.WriteString("kvs") + sbKvs.WriteByte('"') + sbKvs.WriteByte(':') + sbKvs.Write(toSafeJSONString(fmt.Sprintf("%+v", keysAndValues))) + } 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) + } + sbKvs.WriteByte(',') + sbKvs.Write(toSafeJSONString(kStr)) + sbKvs.WriteByte(':') + switch v.(type) { + case string: + sbKvs.Write(toSafeJSONString(v.(string))) + case error: + sbKvs.Write(toSafeJSONString(v.(error).Error())) + default: + if vbs, err := json.Marshal(v); err != nil { + sbKvs.Write(toSafeJSONString(fmt.Sprintf("%+v", v))) + } else { + sbKvs.Write(vbs) + } + } + i = i + 2 + } + } + + sbLogJson.WriteString(sbKvs.String()) + sbLogJson.WriteString("}") + + sbError := strings.Builder{} + sbError.Grow(defaultLogMsgBufferSize) + if err != nil && err.Error() != "" { + sbError.WriteString(" | ") + sbError.WriteString(err.Error()) + sbError.WriteString("\n") + + if errWithStack { + sbError.WriteString("[ ") + sbError.WriteString("ERROR CAUSES WITH STACK") + sbError.WriteString(" ]") + sbError.WriteString("[ ") + sbError.WriteString(fmt.Sprintf("%+v", err)) + sbError.WriteString(" ]") + sbError.WriteString("[ ") + sbError.WriteString("ERROR CAUSES WITH STACK END") + sbError.WriteString(" ]") + } + } + + sbLog := strings.Builder{} + sbLog.Grow(sbLevel.Len() + sbTimestamp.Len() + sbCaller.Len() + sbLogJson.Len() + sbError.Len() + 4) + sbLog.WriteString(sbLevel.String()) + sbLog.WriteString(sbTimestamp.String()) + sbLog.WriteString(sbCaller.String()) + sbLog.WriteString(sbLogJson.String()) + + if sbError.Len() > 0 { + sbLog.WriteString(sbError.String()) + } + + sbLog.WriteString("\n") + return sbLog.String() +} diff --git a/pkg/common/logging/logger_default.go b/pkg/common/logging/logger_default.go new file mode 100644 index 0000000..718476f --- /dev/null +++ b/pkg/common/logging/logger_default.go @@ -0,0 +1,160 @@ +// Copyright 2022, OpenSergo Authors +// +// 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 ( + "github.com/pkg/errors" + "log" + "os" + "path/filepath" +) + +// Level represents the level of logging. +type Level uint8 + +const ( + DebugLevel Level = iota + InfoLevel + WarnLevel + ErrorLevel +) + +type LogFormat uint8 + +const ( + JsonFormat LogFormat = iota + SeparateFormat +) + +var ( + ConsoleLogFormat = JsonFormat + ConsoleLogLevel = InfoLevel +) + +const ( + DefaultLogCallerDepth = 8 + DefaultErrorWithStack = false +) + +// DefaultLogger default logger implements Logger +type DefaultLogger struct { + Logger *log.Logger + LoggerLevel Level + LoggerFormat LogFormat + ErrorWithStack bool +} + +func (l *DefaultLogger) Print(msg string) { + l.Logger.Println(msg) +} + +func (l *DefaultLogger) Debug(msg string, keysAndValues ...interface{}) { + DebugWithCallerDepth(l, l.LoggerFormat, DefaultLogCallerDepth+1, msg, keysAndValues...) +} + +func (l *DefaultLogger) DebugEnabled() bool { + return DebugLevel >= l.LoggerLevel +} + +func (l *DefaultLogger) Info(msg string, keysAndValues ...interface{}) { + InfoWithCallerDepth(l, l.LoggerFormat, DefaultLogCallerDepth+1, msg, keysAndValues...) +} + +func (l *DefaultLogger) InfoEnabled() bool { + return InfoLevel >= l.LoggerLevel +} + +func (l *DefaultLogger) Warn(msg string, keysAndValues ...interface{}) { + WarnWithCallerDepth(l, l.LoggerFormat, DefaultLogCallerDepth+1, msg, keysAndValues...) +} + +func (l *DefaultLogger) WarnEnabled() bool { + return WarnLevel >= l.LoggerLevel +} + +func (l *DefaultLogger) Error(err error, msg string, keysAndValues ...interface{}) { + ErrorWithCallerDepth(l, l.LoggerFormat, DefaultLogCallerDepth+1, err, l.ErrorWithStack, msg, keysAndValues...) +} + +func (l *DefaultLogger) ErrorEnabled() bool { + return ErrorLevel >= l.LoggerLevel +} + +// NewDefaultFileLogger +// filePath $HOME/logs/opensergo/opensergo-universal-transport-service.log +// loggerName FileLoggerName +// LoggerLevel DefaultLogLevel +func NewDefaultFileLogger(logLevel Level) (Logger, error) { + // log dir of 'OpenSergo universal transport service' + userHomeDir, err := os.UserHomeDir() + if err != nil { + userHomeDir = "" + } + filePath := filepath.Join(userHomeDir, "logs", "opensergo", "opensergo-universal-transport-service.log") + mkdirLogFile(filePath) + + return NewFileLogger(filePath, logLevel, JsonFormat, DefaultErrorWithStack) +} + +func mkdirLogFile(filePath string) { + dir := filepath.Dir(filePath) + + if _, err := os.Stat(dir); err != nil { + if os.IsNotExist(err) { + if err := os.MkdirAll(dir, os.ModePerm); err != nil { + Error(err, dir+" create directory error!") + } + } + } + if fileInfo, _ := os.Stat(dir); !fileInfo.IsDir() { + Error(errors.New(dir+" is not a directiry!"), dir+" is not a directiry!") + } + +} + +// NewFileLogger +// filepath is the full path(absolute path). eg: /root/logs/opensergo/opensergo-universal-transport-service.log +func NewFileLogger(filepath string, loggerLevel Level, logFormat LogFormat, errorWithStack bool) (Logger, error) { + mkdirLogFile(filepath) + logFile, err := os.OpenFile(filepath, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666) + if err != nil { + return nil, err + } + defaultLogger := &DefaultLogger{ + Logger: log.New(logFile, "", 0), + LoggerLevel: loggerLevel, + LoggerFormat: logFormat, + ErrorWithStack: errorWithStack, + } + AppendLoggerSlice(defaultLogger) + return defaultLogger, err +} + +func NewDefaultConsoleLogger(logLevel Level) Logger { + defaultLogger := NewConsoleLogger(logLevel, ConsoleLogFormat, DefaultErrorWithStack) + SetConsoleLogger(defaultLogger) + return defaultLogger +} + +func NewConsoleLogger(logLevel Level, logFormat LogFormat, errorWithStack bool) Logger { + defaultLogger := &DefaultLogger{ + Logger: log.New(os.Stdout, "", 0), + LoggerLevel: logLevel, + LoggerFormat: logFormat, + ErrorWithStack: errorWithStack, + } + SetConsoleLogger(defaultLogger) + return defaultLogger +} diff --git a/pkg/common/logging/samples/sample_print_impl_logging/logging_by_sentinel_implement.go b/pkg/common/logging/samples/sample_print_impl_logging/logging_by_sentinel_implement.go new file mode 100644 index 0000000..2f7c26a --- /dev/null +++ b/pkg/common/logging/samples/sample_print_impl_logging/logging_by_sentinel_implement.go @@ -0,0 +1,147 @@ +// Copyright 2022, OpenSergo Authors +// +// 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 main + +import ( + "github.com/opensergo/opensergo-control-plane/pkg/common/logging" + "log" + "os" +) + +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 +} + +type Level uint8 + +const ( + DebugLevel Level = iota + InfoLevel + WarnLevel + ErrorLevel +) + +type SentinelLogger struct { + l *log.Logger + loggerLevel Level +} + +func (sl *SentinelLogger) Debug(msg string, keysAndValues ...interface{}) { + sl.l.Print(msg) +} + +func (sl *SentinelLogger) DebugEnabled() bool { + return DebugLevel >= sl.loggerLevel +} + +func (sl *SentinelLogger) Info(msg string, keysAndValues ...interface{}) { + sl.l.Print(msg) + +} +func (sl *SentinelLogger) InfoEnabled() bool { + return InfoLevel >= sl.loggerLevel +} + +func (sl *SentinelLogger) Warn(msg string, keysAndValues ...interface{}) { + sl.l.Print(msg) +} + +func (sl *SentinelLogger) WarnEnabled() bool { + return WarnLevel >= sl.loggerLevel +} + +func (sl *SentinelLogger) Error(err error, msg string, keysAndValues ...interface{}) { + sl.l.Print(msg) +} + +func (sl *SentinelLogger) ErrorEnabled() bool { + return ErrorLevel >= sl.loggerLevel +} + +func NewDefaultSentinelLogger() *SentinelLogger { + return &SentinelLogger{ + l: log.New(os.Stdout, "", 0), + loggerLevel: DebugLevel, + } +} + +type LoggerAdaptor struct { + l *SentinelLogger + loggerLevel logging.Level + loggerFormat logging.LogFormat + errorWithStack bool +} + +func (la *LoggerAdaptor) Print(msg string) { + logger := log.New(os.Stdout, "", 0) + logger.Print(msg) +} + +func (la *LoggerAdaptor) Debug(msg string, keysAndValues ...interface{}) { + la.l.Debug(msg, keysAndValues) +} + +func (la *LoggerAdaptor) DebugEnabled() bool { + return la.l.DebugEnabled() +} + +func (la *LoggerAdaptor) Info(msg string, keysAndValues ...interface{}) { + la.l.Info(msg, keysAndValues) +} + +func (la *LoggerAdaptor) InfoEnabled() bool { + return la.l.InfoEnabled() +} + +func (la *LoggerAdaptor) Warn(msg string, keysAndValues ...interface{}) { + la.l.Warn(msg, keysAndValues) +} + +func (la *LoggerAdaptor) WarnEnabled() bool { + return la.l.WarnEnabled() +} + +func (la *LoggerAdaptor) Error(err error, msg string, keysAndValues ...interface{}) { + la.l.Error(err, msg, keysAndValues) +} + +func (la *LoggerAdaptor) ErrorEnabled() bool { + return la.l.ErrorEnabled() +} + +func NewLoggerAdaptor() *LoggerAdaptor { + return &LoggerAdaptor{ + l: NewDefaultSentinelLogger(), + loggerLevel: logging.DebugLevel, + loggerFormat: logging.JsonFormat, + errorWithStack: true, + } +} diff --git a/pkg/common/logging/samples/sample_print_impl_logging/main.go b/pkg/common/logging/samples/sample_print_impl_logging/main.go new file mode 100644 index 0000000..018b33d --- /dev/null +++ b/pkg/common/logging/samples/sample_print_impl_logging/main.go @@ -0,0 +1,35 @@ +// Copyright 2022, OpenSergo Authors +// +// 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 main + +import ( + "github.com/opensergo/opensergo-control-plane/pkg/common/logging" + "github.com/pkg/errors" +) + +// when application has use logging-component in opensergo-go-common +// and import the other logging-component such as sentinel logger +// how to print logs with the sentinel logger through logging-component in opensergo-go-common +func main() { + logger, _ := logging.NewDefaultFileLogger(logging.InfoLevel) + logging.AppendLoggerSlice(logger) + + adaptor := NewLoggerAdaptor() + logging.ClearLoggerSlice() + logging.AppendLoggerSlice(adaptor) + logging.Error(errors.New("errors.New"), "this is error log implement logging in sentinelLoggerAdaptor") + logging.Info("this is info log implement logging in sentinelLoggerAdaptor") + logging.Debug("this is debug log implement logging in sentinelLoggerAdaptor") +} diff --git a/pkg/common/logging/samples/sample_print_use_logging/main.go b/pkg/common/logging/samples/sample_print_use_logging/main.go new file mode 100644 index 0000000..6963ef9 --- /dev/null +++ b/pkg/common/logging/samples/sample_print_use_logging/main.go @@ -0,0 +1,31 @@ +// Copyright 2022, OpenSergo Authors +// +// 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 main + +import ( + "github.com/opensergo/opensergo-control-plane/pkg/common/logging" + "github.com/pkg/errors" +) + +// when application has use logging-component Self-contained +// and import logging-component in opensergo-go-common at the same time +// how to print logs with the format from logging-component in opensergo-go-common through logging-component Self-contained +func main() { + logger := logging.NewConsoleLogger(logging.DebugLevel, logging.JsonFormat, true) + sentinelLogger := NewSentinelLogger(logger, logging.JsonFormat, true) + sentinelLogger.Error(errors.New("errors.New"), "this is error log use logging in sentinelLogger") + sentinelLogger.Info("this is info log use logging in sentinelLogger") + sentinelLogger.Debug("this is debug log use logging in sentinelLogger") +} diff --git a/pkg/common/logging/samples/sample_print_use_logging/sentinel_log_use_logging.go b/pkg/common/logging/samples/sample_print_use_logging/sentinel_log_use_logging.go new file mode 100644 index 0000000..5ec6850 --- /dev/null +++ b/pkg/common/logging/samples/sample_print_use_logging/sentinel_log_use_logging.go @@ -0,0 +1,83 @@ +// Copyright 2022, OpenSergo Authors +// +// 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 main + +import "github.com/opensergo/opensergo-control-plane/pkg/common/logging" + +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 +} + +type SentinelLogger struct { + l logging.Logger + logFormat logging.LogFormat + errorWithStack bool +} + +func (sl *SentinelLogger) Debug(msg string, keysAndValues ...interface{}) { + logging.DebugWithCallerDepth(sl.l, sl.logFormat, 5, msg, keysAndValues) +} + +func (sl *SentinelLogger) DebugEnabled() bool { + return sl.l.DebugEnabled() +} + +func (sl *SentinelLogger) Info(msg string, keysAndValues ...interface{}) { + logging.InfoWithCallerDepth(sl.l, sl.logFormat, 5, msg, keysAndValues) + +} +func (sl *SentinelLogger) InfoEnabled() bool { + return sl.l.InfoEnabled() +} + +func (sl *SentinelLogger) Warn(msg string, keysAndValues ...interface{}) { + logging.WarnWithCallerDepth(sl.l, sl.logFormat, 5, msg, keysAndValues) +} + +func (sl *SentinelLogger) WarnEnabled() bool { + return sl.l.WarnEnabled() +} + +func (sl *SentinelLogger) Error(err error, msg string, keysAndValues ...interface{}) { + logging.ErrorWithCallerDepth(sl.l, sl.logFormat, 5, err, sl.errorWithStack, msg, keysAndValues) +} + +func (sl *SentinelLogger) ErrorEnabled() bool { + return sl.l.ErrorEnabled() +} + +func NewSentinelLogger(logger logging.Logger, logFormat logging.LogFormat, errorWithStack bool) *SentinelLogger { + return &SentinelLogger{ + l: logger, + logFormat: logFormat, + errorWithStack: errorWithStack, + } +} diff --git a/pkg/common/logging/samples/sample_simple_print/main.go b/pkg/common/logging/samples/sample_simple_print/main.go new file mode 100644 index 0000000..aa0dcbc --- /dev/null +++ b/pkg/common/logging/samples/sample_simple_print/main.go @@ -0,0 +1,43 @@ +// Copyright 2022, OpenSergo Authors +// +// 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 main + +import ( + "github.com/opensergo/opensergo-control-plane/pkg/common/logging" + "github.com/pkg/errors" +) + +// use default logger to print log +func main() { + printErrorStack() + + logging.Print("--------------------") + + printDefault() +} + +func printErrorStack() { + logging.NewConsoleLogger(logging.DebugLevel, logging.JsonFormat, true) + logging.Error(errors.New("errors.New"), "this is error log in printErrorStack()") + logging.Info("this is info log in printErrorStack()") + logging.Debug("this is debug log in printErrorStack()") +} + +func printDefault() { + logging.NewDefaultConsoleLogger(logging.DebugLevel) + logging.Error(errors.New("errors.New"), "this is error log in printDefault()") + logging.Info("this is info log in printDefault()") + logging.Debug("this is debug log in printDefault()") +} diff --git a/pkg/controller/k8s_operator.go b/pkg/controller/k8s_operator.go index 96f628e..3b94af3 100644 --- a/pkg/controller/k8s_operator.go +++ b/pkg/controller/k8s_operator.go @@ -19,9 +19,8 @@ import ( "sync" "sync/atomic" - "github.com/alibaba/sentinel-golang/logging" - "github.com/alibaba/sentinel-golang/util" crdv1alpha1 "github.com/opensergo/opensergo-control-plane/pkg/api/v1alpha1" + "github.com/opensergo/opensergo-control-plane/pkg/common/logging" "github.com/opensergo/opensergo-control-plane/pkg/model" "github.com/pkg/errors" "k8s.io/apimachinery/pkg/runtime" @@ -76,8 +75,8 @@ type KubernetesOperator struct { // NewKubernetesOperator creates a OpenSergo Kubernetes operator. func NewKubernetesOperator(sendDataHandler model.DataEntirePushHandler) (*KubernetesOperator, error) { ctrl.SetLogger(&k8SLogger{ - l: logging.GetGlobalLogger(), - level: logging.GetGlobalLoggerLevel(), + l: logging.NewConsoleLogger(logging.DebugLevel, logging.JsonFormat, false), + level: logging.ConsoleLogLevel, names: make([]string, 0), keysAndValues: make([]interface{}, 0), }) @@ -210,16 +209,26 @@ func (k *KubernetesOperator) ComponentName() string { func (k *KubernetesOperator) Run() error { // +kubebuilder:scaffold:builder - go util.RunWithRecover(func() { + go runWithRecover(func() { setupLog.Info("Starting OpenSergo operator") if err := k.crdManager.Start(k.ctx); err != nil { setupLog.Error(err, "problem running OpenSergo operator") } setupLog.Info("OpenSergo operator will be closed") }) + return nil } +func runWithRecover(f func()) { + defer func() { + if err := recover(); err != nil { + logging.Error(errors.Errorf("%+v", err), "Unexpected panic in util.RunWithRecover()") + } + }() + f() +} + func (k *KubernetesOperator) GetWatcher(kind string) (*CRDWatcher, bool) { k.controllerMux.RLock() defer k.controllerMux.RUnlock() diff --git a/pkg/controller/log_adapter.go b/pkg/controller/log_adapter.go index d261163..d20fab0 100644 --- a/pkg/controller/log_adapter.go +++ b/pkg/controller/log_adapter.go @@ -17,8 +17,8 @@ package controller import ( "strings" - "github.com/alibaba/sentinel-golang/logging" "github.com/go-logr/logr" + "github.com/opensergo/opensergo-control-plane/pkg/common/logging" ) // noopLogger is a logr.Logger that's always disabled, and does nothing. @@ -61,13 +61,13 @@ func (k *k8SLogger) Info(msg string, keysAndValues ...interface{}) { keysAndValues = append(keysAndValues, k.keysAndValues...) switch k.level { case logging.WarnLevel: - k.l.Warn(k.buildNames()+msg, keysAndValues...) + logging.WarnWithCallerDepth(k.l, logging.JsonFormat, 6, k.buildNames()+msg, keysAndValues...) case logging.InfoLevel: - k.l.Info(k.buildNames()+msg, keysAndValues...) + logging.InfoWithCallerDepth(k.l, logging.JsonFormat, 6, k.buildNames()+msg, keysAndValues...) case logging.DebugLevel: - k.l.Debug(k.buildNames()+msg, keysAndValues...) + logging.DebugWithCallerDepth(k.l, logging.JsonFormat, 6, k.buildNames()+msg, keysAndValues...) default: - k.l.Info(k.buildNames()+msg, keysAndValues...) + logging.InfoWithCallerDepth(k.l, logging.JsonFormat, 6, k.buildNames()+msg, keysAndValues...) } }