Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feature: Add fundamental logger mechanism (#16) #19

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,10 @@
OpenSergo control plane enables unified management for microservice governance rules with OpenSergo CRD (under Kubernetes).

![arch](https://user-images.githubusercontent.com/9434884/182856237-8ce85f41-1a1a-4a2a-8f58-db042bd4db42.png)



## Feature
### logger mechanism
Provider a common Logger interface which is highly extensible and multi implementable.
For detail please refer to [logging/README.md](./pkg/common/logging/README.md)
1 change: 0 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
94 changes: 94 additions & 0 deletions pkg/common/logging/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
# logging component

here provider a common Logger interface which is highly extensible and multi implementable

## Quick start
In this logging component, you can have only one global ConsoleLogger, and several FileLogger, By invoking function in [logger.go](./logger.go):
``` go
// AppendLoggerSlice add the Logger into loggerSlice
func AppendLoggerSlice(loggerAppend Logger) {
loggerSlice = append(loggerSlice, loggerAppend)
}

// SetConsoleLogger set the consoleLogger to print int stdout
func SetConsoleLogger(logger Logger) {
consoleLogger = logger
}
```

You can use the default implement of Logger directly, By new Logger instance implemented in [logger_default.go](./logger_default.go),
But first, you should new instances from [logger_default.go](./logger_default.go), like following:
``` go
// new instances
logging.NewDefaultConsoleLogger(logging.DebugLevel)
logging.NewDefaultFileLogger(logging.DebugLevel)
// invoke the function in Logger interface
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()")
```

You can format you log, by using [logger_assembler.go](./logger_assembler.go),
the assembler provides AssembleMsgJsonFormat and AssembleMsgSeparateFormat can be chosen.

``` go
// the unified entrance for AssembleMsg
func AssembleMsg(logFormat LogFormat, callerDepth int, logLevel, msg string, err error, errWithStack bool, keysAndValues ...interface{}) string

// 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

// 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{})
```

## Samples

### sample_simple_print
If you only want to use the Logger provided by default, you can new a ConsoleLogger, like following:
``` go
// default ConsoleLogger
// logging.NewDefaultConsoleLogger(logging.DebugLevel)
// custom ConsoleLogger
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()")
```
For detail, please refer to [sample_simple_print](./samples/sample_simple_print)

### sample_print_impl_logging
If you want to replace the logger implement where has integrated this logging component,
you can implement the [Logger](./logger.go) by your-self, append you Logger after invoke `ClearLoggerSlice()` in Logger,
or invoke `SetConsoleLogger(logger Logger)` to set the global ConsoleLogger.

For detail, please refer to [sample_print_impl_logging](./samples/sample_print_impl_logging)

### sample_print_use_logging
If you want to use this logging component to replace the other has already integrated,
you can re-implement the other Logger by invoking the function in Logger which was instanced.

For detail, please refer to [sample_print_use_logging](./samples/sample_print_use_logging)
16 changes: 16 additions & 0 deletions pkg/common/logging/doc.go
Original file line number Diff line number Diff line change
@@ -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
252 changes: 252 additions & 0 deletions pkg/common/logging/logger.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,252 @@
// 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"
)

// Logger the common interface for logging.
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
)

// Print logs message no format as what the msg presents.
func Print(msg string) {
doLog("Print", nil, msg)
}

// 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.
func Debug(msg string, keysAndValues ...interface{}) {
doLog("Debug", nil, msg, keysAndValues...)
}

// DebugWithCallerDepth logs a non-error message with the given key/value pairs as context.
//
// logCallerDepth: to calculate the caller:line
//
// 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.
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...))
}

// 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.
func Info(msg string, keysAndValues ...interface{}) {
doLog("Info", nil, msg, keysAndValues...)
}

// InfoWithCallerDepth logs a non-error message with the given key/value pairs as context.
//
// logCallerDepth: to calculate the caller:line
//
// 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.
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...))
}

// 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.
func Warn(msg string, keysAndValues ...interface{}) {
doLog("Warn", nil, msg, keysAndValues...)
}

// WarnWithCallerDepth logs a non-error message with the given key/value pairs as context.
//
// logCallerDepth: to calculate the caller:line
//
// 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.
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...))
}

// 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.
func Error(err error, msg string, keysAndValues ...interface{}) {
doLog("Error", err, msg, keysAndValues...)
}

// ErrorWithCallerDepth logs an error message with error and the given key/value pairs as context.
//
// logCallerDepth: to calculate the caller:line
//
// 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.
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)
}
Loading