Moving kopia logging to remove kopia from indirect dependency in velero plugins

when running `go mod why -m github.com/kopia/kopia` in velero-plugins prior to this change you will see following

```
❯ go mod why -m github.com/kopia/kopia
github.com/konveyor/openshift-velero-plugin/velero-plugins
github.com/vmware-tanzu/velero/pkg/plugin/framework
github.com/vmware-tanzu/velero/pkg/util/logging
github.com/kopia/kopia/repo/logging
```

after
```
❯ go mod why -m github.com/kopia/kopia
(main module does not need module github.com/kopia/kopia)
```

Signed-off-by: Tiger Kaovilai <tkaovila@redhat.com>
This commit is contained in:
Tiger Kaovilai
2023-07-11 17:45:39 -04:00
parent 49e80580b7
commit 05722876b9
5 changed files with 21 additions and 20 deletions

162
pkg/kopia/kopia_log.go Normal file
View File

@@ -0,0 +1,162 @@
package kopia
/*
Copyright the Velero contributors.
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.
*/
import (
"context"
"github.com/kopia/kopia/repo/logging"
"github.com/sirupsen/logrus"
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
)
type kopiaLog struct {
module string
logger logrus.FieldLogger
}
// SetupKopiaLog sets the Kopia log handler to the specific context, Kopia modules
// call the logger in the context to write logs
func SetupKopiaLog(ctx context.Context, logger logrus.FieldLogger) context.Context {
return logging.WithLogger(ctx, func(module string) logging.Logger {
kpLog := &kopiaLog{module, logger}
return zap.New(kpLog).Sugar()
})
}
// Enabled decides whether a given logging level is enabled when logging a message
func (kl *kopiaLog) Enabled(level zapcore.Level) bool {
entry := kl.logger.WithField("null", "null")
switch level {
case zapcore.DebugLevel:
return (entry.Logger.GetLevel() >= logrus.DebugLevel)
case zapcore.InfoLevel:
return (entry.Logger.GetLevel() >= logrus.InfoLevel)
case zapcore.WarnLevel:
return (entry.Logger.GetLevel() >= logrus.WarnLevel)
case zapcore.ErrorLevel:
return (entry.Logger.GetLevel() >= logrus.ErrorLevel)
case zapcore.DPanicLevel:
return (entry.Logger.GetLevel() >= logrus.PanicLevel)
case zapcore.PanicLevel:
return (entry.Logger.GetLevel() >= logrus.PanicLevel)
case zapcore.FatalLevel:
return (entry.Logger.GetLevel() >= logrus.FatalLevel)
default:
return false
}
}
// With adds structured context to the Core.
func (kl *kopiaLog) With(fields []zapcore.Field) zapcore.Core {
copied := kl.logrusFields(fields)
return &kopiaLog{
module: kl.module,
logger: kl.logger.WithFields(copied),
}
}
// Check determines whether the supplied Entry should be logged. If the entry
// should be logged, the Core adds itself to the CheckedEntry and returns the result.
func (kl *kopiaLog) Check(ent zapcore.Entry, ce *zapcore.CheckedEntry) *zapcore.CheckedEntry {
if kl.Enabled(ent.Level) {
return ce.AddCore(ent, kl)
}
return ce
}
// Write serializes the Entry and any Fields supplied at the log site and writes them to their destination.
func (kl *kopiaLog) Write(ent zapcore.Entry, fields []zapcore.Field) error {
copied := kl.logrusFieldsForWrite(ent, fields)
logger := kl.logger.WithFields(copied)
switch ent.Level {
case zapcore.DebugLevel:
logger.Debug(ent.Message)
case zapcore.InfoLevel:
logger.Info(ent.Message)
case zapcore.WarnLevel:
logger.Warn(ent.Message)
case zapcore.ErrorLevel:
// We see Kopia generates error logs for some normal cases or non-critical
// cases. So Kopia's error logs are regarded as warning logs so that they don't
// affect Velero's workflow.
logger.Warn(ent.Message)
case zapcore.DPanicLevel:
logger.Panic(ent.Message)
case zapcore.PanicLevel:
logger.Panic(ent.Message)
case zapcore.FatalLevel:
logger.Fatal(ent.Message)
}
return nil
}
// Sync flushes buffered logs (if any).
func (kl *kopiaLog) Sync() error {
return nil
}
func (kl *kopiaLog) logrusFields(fields []zapcore.Field) logrus.Fields {
if fields == nil {
return logrus.Fields{}
}
m := zapcore.NewMapObjectEncoder()
for _, field := range fields {
field.AddTo(m)
}
return m.Fields
}
func (kl *kopiaLog) getLogModule() string {
return "kopia/" + kl.module
}
func (kl *kopiaLog) logrusFieldsForWrite(ent zapcore.Entry, fields []zapcore.Field) logrus.Fields {
copied := kl.logrusFields(fields)
copied["logModule"] = kl.getLogModule()
if ent.Caller.Function != "" {
copied["function"] = ent.Caller.Function
}
path := ent.Caller.FullPath()
if path != "undefined" {
copied["path"] = path
}
if ent.LoggerName != "" {
copied["logger name"] = ent.LoggerName
}
if ent.Stack != "" {
copied["stack"] = ent.Stack
}
if ent.Level == zap.ErrorLevel {
copied["sublevel"] = "error"
}
return copied
}

349
pkg/kopia/kopia_log_test.go Normal file
View File

@@ -0,0 +1,349 @@
/*
Copyright the Velero contributors.
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 kopia
import (
"testing"
"github.com/sirupsen/logrus"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.uber.org/zap/zapcore"
"github.com/vmware-tanzu/velero/pkg/test"
)
func TestEnabled(t *testing.T) {
testCases := []struct {
name string
level logrus.Level
zapLevel zapcore.Level
expected bool
}{
{
name: "check debug again debug",
level: logrus.DebugLevel,
zapLevel: zapcore.DebugLevel,
expected: true,
},
{
name: "check debug again info",
level: logrus.InfoLevel,
zapLevel: zapcore.DebugLevel,
expected: false,
},
{
name: "check info again debug",
level: logrus.DebugLevel,
zapLevel: zapcore.InfoLevel,
expected: true,
},
{
name: "check info again info",
level: logrus.InfoLevel,
zapLevel: zapcore.InfoLevel,
expected: true,
},
{
name: "check warn again warn",
level: logrus.WarnLevel,
zapLevel: zapcore.WarnLevel,
expected: true,
},
{
name: "check info again error",
level: logrus.ErrorLevel,
zapLevel: zapcore.InfoLevel,
expected: false,
},
{
name: "check error again error",
level: logrus.ErrorLevel,
zapLevel: zapcore.ErrorLevel,
expected: true,
},
{
name: "check dppanic again panic",
level: logrus.PanicLevel,
zapLevel: zapcore.DPanicLevel,
expected: true,
},
{
name: "check panic again error",
level: logrus.ErrorLevel,
zapLevel: zapcore.PanicLevel,
expected: true,
},
{
name: "check fatal again fatal",
level: logrus.FatalLevel,
zapLevel: zapcore.FatalLevel,
expected: true,
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
log := kopiaLog{
logger: test.NewLoggerWithLevel(tc.level),
}
m := log.Enabled(tc.zapLevel)
require.Equal(t, tc.expected, m)
})
}
}
func TestLogrusFieldsForWrite(t *testing.T) {
testCases := []struct {
name string
module string
zapEntry zapcore.Entry
zapFields []zapcore.Field
expected logrus.Fields
}{
{
name: "debug with nil fields",
module: "module-01",
zapEntry: zapcore.Entry{
Level: zapcore.DebugLevel,
},
zapFields: nil,
expected: logrus.Fields{
"logModule": "kopia/module-01",
},
},
{
name: "error with nil fields",
module: "module-02",
zapEntry: zapcore.Entry{
Level: zapcore.ErrorLevel,
},
zapFields: nil,
expected: logrus.Fields{
"logModule": "kopia/module-02",
"sublevel": "error",
},
},
{
name: "info with nil string filed",
module: "module-03",
zapEntry: zapcore.Entry{
Level: zapcore.InfoLevel,
},
zapFields: []zapcore.Field{
{
Key: "key-01",
Type: zapcore.StringType,
String: "value-01",
},
},
expected: logrus.Fields{
"logModule": "kopia/module-03",
"key-01": "value-01",
},
},
{
name: "info with logger name",
module: "module-04",
zapEntry: zapcore.Entry{
Level: zapcore.InfoLevel,
LoggerName: "logger-name-01",
},
zapFields: nil,
expected: logrus.Fields{
"logModule": "kopia/module-04",
"logger name": "logger-name-01",
},
},
{
name: "info with function name",
module: "module-05",
zapEntry: zapcore.Entry{
Level: zapcore.InfoLevel,
Caller: zapcore.EntryCaller{
Function: "function-name-01",
},
},
zapFields: nil,
expected: logrus.Fields{
"logModule": "kopia/module-05",
"function": "function-name-01",
},
},
{
name: "info with undefined path",
module: "module-06",
zapEntry: zapcore.Entry{
Level: zapcore.InfoLevel,
Caller: zapcore.EntryCaller{
Defined: false,
},
},
zapFields: nil,
expected: logrus.Fields{
"logModule": "kopia/module-06",
},
},
{
name: "info with defined path",
module: "module-06",
zapEntry: zapcore.Entry{
Level: zapcore.InfoLevel,
Caller: zapcore.EntryCaller{
Defined: true,
File: "file-name-01",
Line: 100,
},
},
zapFields: nil,
expected: logrus.Fields{
"logModule": "kopia/module-06",
"path": "file-name-01:100",
},
},
{
name: "info with stack",
module: "module-07",
zapEntry: zapcore.Entry{
Level: zapcore.InfoLevel,
Stack: "fake-stack",
},
zapFields: nil,
expected: logrus.Fields{
"logModule": "kopia/module-07",
"stack": "fake-stack",
},
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
log := kopiaLog{
module: tc.module,
logger: test.NewLogger(),
}
m := log.logrusFieldsForWrite(tc.zapEntry, tc.zapFields)
require.Equal(t, tc.expected, m)
})
}
}
func TestWrite(t *testing.T) {
testCases := []struct {
name string
ent zapcore.Entry
logMessage string
logLevel string
shouldPanic bool
}{
{
name: "write debug",
ent: zapcore.Entry{
Level: zapcore.DebugLevel,
Message: "fake-message",
},
logMessage: "fake-message",
logLevel: "level=debug",
},
{
name: "write info",
ent: zapcore.Entry{
Level: zapcore.InfoLevel,
Message: "fake-message",
},
logMessage: "fake-message",
logLevel: "level=info",
},
{
name: "write warn",
ent: zapcore.Entry{
Level: zapcore.WarnLevel,
Message: "fake-message",
},
logMessage: "fake-message",
logLevel: "level=warn",
},
{
name: "write error",
ent: zapcore.Entry{
Level: zapcore.ErrorLevel,
Message: "fake-message",
},
logMessage: "fake-message",
logLevel: "level=warn",
},
{
name: "write DPanic",
ent: zapcore.Entry{
Level: zapcore.DPanicLevel,
Message: "fake-message",
},
logMessage: "fake-message",
logLevel: "level=panic",
shouldPanic: true,
},
{
name: "write panic",
ent: zapcore.Entry{
Level: zapcore.PanicLevel,
Message: "fake-message",
},
logMessage: "fake-message",
logLevel: "level=panic",
shouldPanic: true,
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
logMessage := ""
log := kopiaLog{
logger: test.NewSingleLogger(&logMessage),
}
if tc.shouldPanic {
defer func() {
r := recover()
assert.NotNil(t, r)
if len(tc.logMessage) > 0 {
assert.Contains(t, logMessage, tc.logMessage)
}
if len(tc.logLevel) > 0 {
assert.Contains(t, logMessage, tc.logLevel)
}
}()
}
err := log.Write(tc.ent, nil)
assert.NoError(t, err)
if len(tc.logMessage) > 0 {
assert.Contains(t, logMessage, tc.logMessage)
}
if len(tc.logLevel) > 0 {
assert.Contains(t, logMessage, tc.logLevel)
}
})
}
}