[VOL-3678] First implementation of the BBSim-sadis-server
Change-Id: I5077a8f861f4cc6af9759f31a4a415042c05eba3
diff --git a/vendor/github.com/opencord/voltha-lib-go/v4/pkg/log/common.go b/vendor/github.com/opencord/voltha-lib-go/v4/pkg/log/common.go
new file mode 100644
index 0000000..b0ce81b
--- /dev/null
+++ b/vendor/github.com/opencord/voltha-lib-go/v4/pkg/log/common.go
@@ -0,0 +1,27 @@
+/*
+ * Copyright 2020-present Open Networking Foundation
+ *
+ * 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 log
+
+var logger CLogger
+
+func init() {
+ // Setup this package so that it's log level can be modified at run time
+ var err error
+ logger, err = RegisterPackage(JSON, ErrorLevel, Fields{})
+ if err != nil {
+ panic(err)
+ }
+}
diff --git a/vendor/github.com/opencord/voltha-lib-go/v4/pkg/log/log.go b/vendor/github.com/opencord/voltha-lib-go/v4/pkg/log/log.go
new file mode 100644
index 0000000..b8d498c
--- /dev/null
+++ b/vendor/github.com/opencord/voltha-lib-go/v4/pkg/log/log.go
@@ -0,0 +1,661 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+
+ * 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 log provides a structured Logger interface implemented using zap logger. It provides the following capabilities:
+// 1. Package level logging - a go package can register itself (AddPackage) and have a logger created for that package.
+// 2. Dynamic log level change - for all registered packages (SetAllLogLevel)
+// 3. Dynamic log level change - for a given package (SetPackageLogLevel)
+// 4. Provides a default logger for unregistered packages (however avoid its usage)
+// 5. Allow key-value pairs to be added to a logger(UpdateLogger) or all loggers (UpdateAllLoggers) at run time
+// 6. Add to the log output the location where the log was invoked (filename.functionname.linenumber)
+//
+// Using package-level logging (recommended approach). In the examples below, log refers to this log package.
+//
+// 1. In the appropriate package, add the following in the init section of the package (usually in a common.go file)
+// The log level can be changed and any number of default fields can be added as well. The log level specifies
+// the lowest log level that will be in the output while the fields will be automatically added to all log printouts.
+// However, as voltha components re-initialize the log level of each registered package to default initial loglevel
+// passed as CLI argument, the log level passed in RegisterPackage call effectively has no effect.
+//
+// var logger log.CLogger
+// func init() {
+// logger, err = log.RegisterPackage(log.JSON, log.ErrorLevel, log.Fields{"key1": "value1"})
+// }
+//
+// 2. In the calling package, use any of the publicly available functions of local package-level logger instance created
+// in previous step. Here is an example to write an Info log with additional fields:
+//
+// logger.Infow("An example", mylog.Fields{"myStringOutput": "output", "myIntOutput": 2})
+//
+// 3. To dynamically change the log level, you can use
+// a) SetLogLevel from inside your package or
+// b) SetPackageLogLevel from anywhere or
+// c) SetAllLogLevel from anywhere.
+//
+// Dynamic Loglevel configuration feature also uses SetPackageLogLevel method based on triggers received due to
+// Changes to configured loglevels
+
+package log
+
+import (
+ "context"
+ "errors"
+ "fmt"
+ zp "go.uber.org/zap"
+ zc "go.uber.org/zap/zapcore"
+ "path"
+ "runtime"
+ "strings"
+)
+
+type LogLevel int8
+
+const (
+ // DebugLevel logs a message at debug level
+ DebugLevel = LogLevel(iota)
+ // InfoLevel logs a message at info level
+ InfoLevel
+ // WarnLevel logs a message at warning level
+ WarnLevel
+ // ErrorLevel logs a message at error level
+ ErrorLevel
+ // FatalLevel logs a message, then calls os.Exit(1).
+ FatalLevel
+)
+
+// CONSOLE formats the log for the console, mostly used during development
+const CONSOLE = "console"
+
+// JSON formats the log using json format, mostly used by an automated logging system consumption
+const JSON = "json"
+
+// Context Aware Logger represents an abstract logging interface. Any logging implementation used
+// will need to abide by this interface
+type CLogger interface {
+ Debug(context.Context, ...interface{})
+ Debugln(context.Context, ...interface{})
+ Debugf(context.Context, string, ...interface{})
+ Debugw(context.Context, string, Fields)
+
+ Info(context.Context, ...interface{})
+ Infoln(context.Context, ...interface{})
+ Infof(context.Context, string, ...interface{})
+ Infow(context.Context, string, Fields)
+
+ Warn(context.Context, ...interface{})
+ Warnln(context.Context, ...interface{})
+ Warnf(context.Context, string, ...interface{})
+ Warnw(context.Context, string, Fields)
+
+ Error(context.Context, ...interface{})
+ Errorln(context.Context, ...interface{})
+ Errorf(context.Context, string, ...interface{})
+ Errorw(context.Context, string, Fields)
+
+ Fatal(context.Context, ...interface{})
+ Fatalln(context.Context, ...interface{})
+ Fatalf(context.Context, string, ...interface{})
+ Fatalw(context.Context, string, Fields)
+
+ With(Fields) CLogger
+
+ // The following are added to be able to use this logger as a gRPC LoggerV2 if needed
+ //
+ Warning(context.Context, ...interface{})
+ Warningln(context.Context, ...interface{})
+ Warningf(context.Context, string, ...interface{})
+
+ // V reports whether verbosity level l is at least the requested verbose level.
+ V(l LogLevel) bool
+
+ //Returns the log level of this specific logger
+ GetLogLevel() LogLevel
+}
+
+// Fields is used as key-value pairs for structured logging
+type Fields map[string]interface{}
+
+var defaultLogger *clogger
+var cfg zp.Config
+
+var loggers map[string]*clogger
+var cfgs map[string]zp.Config
+
+type clogger struct {
+ log *zp.SugaredLogger
+ parent *zp.Logger
+ packageName string
+}
+
+func logLevelToAtomicLevel(l LogLevel) zp.AtomicLevel {
+ switch l {
+ case DebugLevel:
+ return zp.NewAtomicLevelAt(zc.DebugLevel)
+ case InfoLevel:
+ return zp.NewAtomicLevelAt(zc.InfoLevel)
+ case WarnLevel:
+ return zp.NewAtomicLevelAt(zc.WarnLevel)
+ case ErrorLevel:
+ return zp.NewAtomicLevelAt(zc.ErrorLevel)
+ case FatalLevel:
+ return zp.NewAtomicLevelAt(zc.FatalLevel)
+ }
+ return zp.NewAtomicLevelAt(zc.ErrorLevel)
+}
+
+func logLevelToLevel(l LogLevel) zc.Level {
+ switch l {
+ case DebugLevel:
+ return zc.DebugLevel
+ case InfoLevel:
+ return zc.InfoLevel
+ case WarnLevel:
+ return zc.WarnLevel
+ case ErrorLevel:
+ return zc.ErrorLevel
+ case FatalLevel:
+ return zc.FatalLevel
+ }
+ return zc.ErrorLevel
+}
+
+func levelToLogLevel(l zc.Level) LogLevel {
+ switch l {
+ case zc.DebugLevel:
+ return DebugLevel
+ case zc.InfoLevel:
+ return InfoLevel
+ case zc.WarnLevel:
+ return WarnLevel
+ case zc.ErrorLevel:
+ return ErrorLevel
+ case zc.FatalLevel:
+ return FatalLevel
+ }
+ return ErrorLevel
+}
+
+func StringToLogLevel(l string) (LogLevel, error) {
+ switch strings.ToUpper(l) {
+ case "DEBUG":
+ return DebugLevel, nil
+ case "INFO":
+ return InfoLevel, nil
+ case "WARN":
+ return WarnLevel, nil
+ case "ERROR":
+ return ErrorLevel, nil
+ case "FATAL":
+ return FatalLevel, nil
+ }
+ return 0, errors.New("Given LogLevel is invalid : " + l)
+}
+
+func LogLevelToString(l LogLevel) (string, error) {
+ switch l {
+ case DebugLevel:
+ return "DEBUG", nil
+ case InfoLevel:
+ return "INFO", nil
+ case WarnLevel:
+ return "WARN", nil
+ case ErrorLevel:
+ return "ERROR", nil
+ case FatalLevel:
+ return "FATAL", nil
+ }
+ return "", errors.New("Given LogLevel is invalid " + string(l))
+}
+
+func getDefaultConfig(outputType string, level LogLevel, defaultFields Fields) zp.Config {
+ return zp.Config{
+ Level: logLevelToAtomicLevel(level),
+ Encoding: outputType,
+ Development: true,
+ OutputPaths: []string{"stdout"},
+ ErrorOutputPaths: []string{"stderr"},
+ InitialFields: defaultFields,
+ EncoderConfig: zc.EncoderConfig{
+ LevelKey: "level",
+ MessageKey: "msg",
+ TimeKey: "ts",
+ CallerKey: "caller",
+ StacktraceKey: "stacktrace",
+ LineEnding: zc.DefaultLineEnding,
+ EncodeLevel: zc.LowercaseLevelEncoder,
+ EncodeTime: zc.ISO8601TimeEncoder,
+ EncodeDuration: zc.SecondsDurationEncoder,
+ EncodeCaller: zc.ShortCallerEncoder,
+ },
+ }
+}
+
+func ConstructZapConfig(outputType string, level LogLevel, fields Fields) zp.Config {
+ return getDefaultConfig(outputType, level, fields)
+}
+
+// SetLogger needs to be invoked before the logger API can be invoked. This function
+// initialize the default logger (zap's sugaredlogger)
+func SetDefaultLogger(outputType string, level LogLevel, defaultFields Fields) (CLogger, error) {
+ // Build a custom config using zap
+ cfg = getDefaultConfig(outputType, level, defaultFields)
+
+ l, err := cfg.Build(zp.AddCallerSkip(1))
+ if err != nil {
+ return nil, err
+ }
+
+ defaultLogger = &clogger{
+ log: l.Sugar(),
+ parent: l,
+ }
+
+ return defaultLogger, nil
+}
+
+// AddPackage registers a package to the log map. Each package gets its own logger which allows
+// its config (loglevel) to be changed dynamically without interacting with the other packages.
+// outputType is JSON, level is the lowest level log to output with this logger and defaultFields is a map of
+// key-value pairs to always add to the output.
+// Note: AddPackage also returns a reference to the actual logger. If a calling package uses this reference directly
+//instead of using the publicly available functions in this log package then a number of functionalities will not
+// be available to it, notably log tracing with filename.functionname.linenumber annotation.
+//
+// pkgNames parameter should be used for testing only as this function detects the caller's package.
+func RegisterPackage(outputType string, level LogLevel, defaultFields Fields, pkgNames ...string) (CLogger, error) {
+ if cfgs == nil {
+ cfgs = make(map[string]zp.Config)
+ }
+ if loggers == nil {
+ loggers = make(map[string]*clogger)
+ }
+
+ var pkgName string
+ for _, name := range pkgNames {
+ pkgName = name
+ break
+ }
+ if pkgName == "" {
+ pkgName, _, _, _ = getCallerInfo()
+ }
+
+ if _, exist := loggers[pkgName]; exist {
+ return loggers[pkgName], nil
+ }
+
+ cfgs[pkgName] = getDefaultConfig(outputType, level, defaultFields)
+
+ l, err := cfgs[pkgName].Build(zp.AddCallerSkip(1))
+ if err != nil {
+ return nil, err
+ }
+
+ loggers[pkgName] = &clogger{
+ log: l.Sugar(),
+ parent: l,
+ packageName: pkgName,
+ }
+ return loggers[pkgName], nil
+}
+
+//UpdateAllLoggers create new loggers for all registered pacakges with the defaultFields.
+func UpdateAllLoggers(defaultFields Fields) error {
+ for pkgName, cfg := range cfgs {
+ for k, v := range defaultFields {
+ if cfg.InitialFields == nil {
+ cfg.InitialFields = make(map[string]interface{})
+ }
+ cfg.InitialFields[k] = v
+ }
+ l, err := cfg.Build(zp.AddCallerSkip(1))
+ if err != nil {
+ return err
+ }
+
+ // Update the existing zap logger instance
+ loggers[pkgName].log = l.Sugar()
+ loggers[pkgName].parent = l
+ }
+ return nil
+}
+
+// Return a list of all packages that have individually-configured loggers
+func GetPackageNames() []string {
+ i := 0
+ keys := make([]string, len(loggers))
+ for k := range loggers {
+ keys[i] = k
+ i++
+ }
+ return keys
+}
+
+// UpdateLogger updates the logger associated with a caller's package with supplied defaultFields
+func UpdateLogger(defaultFields Fields) error {
+ pkgName, _, _, _ := getCallerInfo()
+ if _, exist := loggers[pkgName]; !exist {
+ return fmt.Errorf("package-%s-not-registered", pkgName)
+ }
+
+ // Build a new logger
+ if _, exist := cfgs[pkgName]; !exist {
+ return fmt.Errorf("config-%s-not-registered", pkgName)
+ }
+
+ cfg := cfgs[pkgName]
+ for k, v := range defaultFields {
+ if cfg.InitialFields == nil {
+ cfg.InitialFields = make(map[string]interface{})
+ }
+ cfg.InitialFields[k] = v
+ }
+ l, err := cfg.Build(zp.AddCallerSkip(1))
+ if err != nil {
+ return err
+ }
+
+ // Update the existing zap logger instance
+ loggers[pkgName].log = l.Sugar()
+ loggers[pkgName].parent = l
+
+ return nil
+}
+
+func setLevel(cfg zp.Config, level LogLevel) {
+ switch level {
+ case DebugLevel:
+ cfg.Level.SetLevel(zc.DebugLevel)
+ case InfoLevel:
+ cfg.Level.SetLevel(zc.InfoLevel)
+ case WarnLevel:
+ cfg.Level.SetLevel(zc.WarnLevel)
+ case ErrorLevel:
+ cfg.Level.SetLevel(zc.ErrorLevel)
+ case FatalLevel:
+ cfg.Level.SetLevel(zc.FatalLevel)
+ default:
+ cfg.Level.SetLevel(zc.ErrorLevel)
+ }
+}
+
+//SetPackageLogLevel dynamically sets the log level of a given package to level. This is typically invoked at an
+// application level during debugging
+func SetPackageLogLevel(packageName string, level LogLevel) {
+ // Get proper config
+ if cfg, ok := cfgs[packageName]; ok {
+ setLevel(cfg, level)
+ }
+}
+
+//SetAllLogLevel sets the log level of all registered packages to level
+func SetAllLogLevel(level LogLevel) {
+ // Get proper config
+ for _, cfg := range cfgs {
+ setLevel(cfg, level)
+ }
+}
+
+//GetPackageLogLevel returns the current log level of a package.
+func GetPackageLogLevel(packageName ...string) (LogLevel, error) {
+ var name string
+ if len(packageName) == 1 {
+ name = packageName[0]
+ } else {
+ name, _, _, _ = getCallerInfo()
+ }
+ if cfg, ok := cfgs[name]; ok {
+ return levelToLogLevel(cfg.Level.Level()), nil
+ }
+ return 0, fmt.Errorf("unknown-package-%s", name)
+}
+
+//GetDefaultLogLevel gets the log level used for packages that don't have specific loggers
+func GetDefaultLogLevel() LogLevel {
+ return levelToLogLevel(cfg.Level.Level())
+}
+
+//SetLogLevel sets the log level for the logger corresponding to the caller's package
+func SetLogLevel(level LogLevel) error {
+ pkgName, _, _, _ := getCallerInfo()
+ if _, exist := cfgs[pkgName]; !exist {
+ return fmt.Errorf("unregistered-package-%s", pkgName)
+ }
+ cfg := cfgs[pkgName]
+ setLevel(cfg, level)
+ return nil
+}
+
+//SetDefaultLogLevel sets the log level used for packages that don't have specific loggers
+func SetDefaultLogLevel(level LogLevel) {
+ setLevel(cfg, level)
+}
+
+// CleanUp flushed any buffered log entries. Applications should take care to call
+// CleanUp before exiting.
+func CleanUp() error {
+ for _, logger := range loggers {
+ if logger != nil {
+ if logger.parent != nil {
+ if err := logger.parent.Sync(); err != nil {
+ return err
+ }
+ }
+ }
+ }
+ if defaultLogger != nil {
+ if defaultLogger.parent != nil {
+ if err := defaultLogger.parent.Sync(); err != nil {
+ return err
+ }
+ }
+ }
+ return nil
+}
+
+func getCallerInfo() (string, string, string, int) {
+ // Since the caller of a log function is one stack frame before (in terms of stack higher level) the log.go
+ // filename, then first look for the last log.go filename and then grab the caller info one level higher.
+ maxLevel := 3
+ skiplevel := 3 // Level with the most empirical success to see the last log.go stack frame.
+ pc := make([]uintptr, maxLevel)
+ n := runtime.Callers(skiplevel, pc)
+ packageName := ""
+ funcName := ""
+ fileName := ""
+ var line int
+ if n == 0 {
+ return packageName, fileName, funcName, line
+ }
+ frames := runtime.CallersFrames(pc[:n])
+ var frame runtime.Frame
+ var foundFrame runtime.Frame
+ more := true
+ for more {
+ frame, more = frames.Next()
+ _, fileName = path.Split(frame.File)
+ if fileName != "log.go" {
+ foundFrame = frame // First frame after log.go in the frame stack
+ break
+ }
+ }
+ parts := strings.Split(foundFrame.Function, ".")
+ pl := len(parts)
+ if pl >= 2 {
+ funcName = parts[pl-1]
+ if parts[pl-2][0] == '(' {
+ packageName = strings.Join(parts[0:pl-2], ".")
+ } else {
+ packageName = strings.Join(parts[0:pl-1], ".")
+ }
+ }
+
+ if strings.HasSuffix(packageName, ".init") {
+ packageName = strings.TrimSuffix(packageName, ".init")
+ }
+
+ if strings.HasSuffix(fileName, ".go") {
+ fileName = strings.TrimSuffix(fileName, ".go")
+ }
+
+ return packageName, fileName, funcName, foundFrame.Line
+}
+
+// With returns a logger initialized with the key-value pairs
+func (l clogger) With(keysAndValues Fields) CLogger {
+ return clogger{log: l.log.With(serializeMap(keysAndValues)...), parent: l.parent}
+}
+
+// Debug logs a message at level Debug on the standard logger.
+func (l clogger) Debug(ctx context.Context, args ...interface{}) {
+ l.log.With(GetGlobalLFM().ExtractContextAttributes(ctx)...).Debug(args...)
+}
+
+// Debugln logs a message at level Debug on the standard logger with a line feed. Default in any case.
+func (l clogger) Debugln(ctx context.Context, args ...interface{}) {
+ l.log.With(GetGlobalLFM().ExtractContextAttributes(ctx)...).Debug(args...)
+}
+
+// Debugw logs a message at level Debug on the standard logger.
+func (l clogger) Debugf(ctx context.Context, format string, args ...interface{}) {
+ l.log.With(GetGlobalLFM().ExtractContextAttributes(ctx)...).Debugf(format, args...)
+}
+
+// Debugw logs a message with some additional context. The variadic key-value
+// pairs are treated as they are in With.
+func (l clogger) Debugw(ctx context.Context, msg string, keysAndValues Fields) {
+ if l.V(DebugLevel) {
+ l.log.With(GetGlobalLFM().ExtractContextAttributes(ctx)...).Debugw(msg, serializeMap(keysAndValues)...)
+ }
+}
+
+// Info logs a message at level Info on the standard logger.
+func (l clogger) Info(ctx context.Context, args ...interface{}) {
+ l.log.With(GetGlobalLFM().ExtractContextAttributes(ctx)...).Info(args...)
+}
+
+// Infoln logs a message at level Info on the standard logger with a line feed. Default in any case.
+func (l clogger) Infoln(ctx context.Context, args ...interface{}) {
+ l.log.With(GetGlobalLFM().ExtractContextAttributes(ctx)...).Info(args...)
+ //msg := fmt.Sprintln(args...)
+ //l.sourced().Info(msg[:len(msg)-1])
+}
+
+// Infof logs a message at level Info on the standard logger.
+func (l clogger) Infof(ctx context.Context, format string, args ...interface{}) {
+ l.log.With(GetGlobalLFM().ExtractContextAttributes(ctx)...).Infof(format, args...)
+}
+
+// Infow logs a message with some additional context. The variadic key-value
+// pairs are treated as they are in With.
+func (l clogger) Infow(ctx context.Context, msg string, keysAndValues Fields) {
+ if l.V(InfoLevel) {
+ l.log.With(GetGlobalLFM().ExtractContextAttributes(ctx)...).Infow(msg, serializeMap(keysAndValues)...)
+ }
+}
+
+// Warn logs a message at level Warn on the standard logger.
+func (l clogger) Warn(ctx context.Context, args ...interface{}) {
+ l.log.With(GetGlobalLFM().ExtractContextAttributes(ctx)...).Warn(args...)
+}
+
+// Warnln logs a message at level Warn on the standard logger with a line feed. Default in any case.
+func (l clogger) Warnln(ctx context.Context, args ...interface{}) {
+ l.log.With(GetGlobalLFM().ExtractContextAttributes(ctx)...).Warn(args...)
+}
+
+// Warnf logs a message at level Warn on the standard logger.
+func (l clogger) Warnf(ctx context.Context, format string, args ...interface{}) {
+ l.log.With(GetGlobalLFM().ExtractContextAttributes(ctx)...).Warnf(format, args...)
+}
+
+// Warnw logs a message with some additional context. The variadic key-value
+// pairs are treated as they are in With.
+func (l clogger) Warnw(ctx context.Context, msg string, keysAndValues Fields) {
+ if l.V(WarnLevel) {
+ l.log.With(GetGlobalLFM().ExtractContextAttributes(ctx)...).Warnw(msg, serializeMap(keysAndValues)...)
+ }
+}
+
+// Error logs a message at level Error on the standard logger.
+func (l clogger) Error(ctx context.Context, args ...interface{}) {
+ l.log.With(GetGlobalLFM().ExtractContextAttributes(ctx)...).Error(args...)
+}
+
+// Errorln logs a message at level Error on the standard logger with a line feed. Default in any case.
+func (l clogger) Errorln(ctx context.Context, args ...interface{}) {
+ l.log.With(GetGlobalLFM().ExtractContextAttributes(ctx)...).Error(args...)
+}
+
+// Errorf logs a message at level Error on the standard logger.
+func (l clogger) Errorf(ctx context.Context, format string, args ...interface{}) {
+ l.log.With(GetGlobalLFM().ExtractContextAttributes(ctx)...).Errorf(format, args...)
+}
+
+// Errorw logs a message with some additional context. The variadic key-value
+// pairs are treated as they are in With.
+func (l clogger) Errorw(ctx context.Context, msg string, keysAndValues Fields) {
+ if l.V(ErrorLevel) {
+ l.log.With(GetGlobalLFM().ExtractContextAttributes(ctx)...).Errorw(msg, serializeMap(keysAndValues)...)
+ }
+}
+
+// Fatal logs a message at level Fatal on the standard logger.
+func (l clogger) Fatal(ctx context.Context, args ...interface{}) {
+ l.log.With(GetGlobalLFM().ExtractContextAttributes(ctx)...).Fatal(args...)
+}
+
+// Fatalln logs a message at level Fatal on the standard logger with a line feed. Default in any case.
+func (l clogger) Fatalln(ctx context.Context, args ...interface{}) {
+ l.log.With(GetGlobalLFM().ExtractContextAttributes(ctx)...).Fatal(args...)
+}
+
+// Fatalf logs a message at level Fatal on the standard logger.
+func (l clogger) Fatalf(ctx context.Context, format string, args ...interface{}) {
+ l.log.With(GetGlobalLFM().ExtractContextAttributes(ctx)...).Fatalf(format, args...)
+}
+
+// Fatalw logs a message with some additional context. The variadic key-value
+// pairs are treated as they are in With.
+func (l clogger) Fatalw(ctx context.Context, msg string, keysAndValues Fields) {
+ if l.V(FatalLevel) {
+ l.log.With(GetGlobalLFM().ExtractContextAttributes(ctx)...).Fatalw(msg, serializeMap(keysAndValues)...)
+ }
+}
+
+// Warning logs a message at level Warn on the standard logger.
+func (l clogger) Warning(ctx context.Context, args ...interface{}) {
+ l.log.With(GetGlobalLFM().ExtractContextAttributes(ctx)...).Warn(args...)
+}
+
+// Warningln logs a message at level Warn on the standard logger with a line feed. Default in any case.
+func (l clogger) Warningln(ctx context.Context, args ...interface{}) {
+ l.log.With(GetGlobalLFM().ExtractContextAttributes(ctx)...).Warn(args...)
+}
+
+// Warningf logs a message at level Warn on the standard logger.
+func (l clogger) Warningf(ctx context.Context, format string, args ...interface{}) {
+ l.log.With(GetGlobalLFM().ExtractContextAttributes(ctx)...).Warnf(format, args...)
+}
+
+// V reports whether verbosity level l is at least the requested verbose level.
+func (l clogger) V(level LogLevel) bool {
+ return l.parent.Core().Enabled(logLevelToLevel(level))
+}
+
+// GetLogLevel returns the current level of the logger
+func (l clogger) GetLogLevel() LogLevel {
+ return levelToLogLevel(cfgs[l.packageName].Level.Level())
+}
diff --git a/vendor/github.com/opencord/voltha-lib-go/v4/pkg/log/utils.go b/vendor/github.com/opencord/voltha-lib-go/v4/pkg/log/utils.go
new file mode 100644
index 0000000..82c3d7d
--- /dev/null
+++ b/vendor/github.com/opencord/voltha-lib-go/v4/pkg/log/utils.go
@@ -0,0 +1,468 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+
+ * 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.
+ */
+
+// File contains utility functions to support Open Tracing in conjunction with
+// Enhanced Logging based on context propagation
+
+package log
+
+import (
+ "context"
+ "errors"
+ "fmt"
+ "github.com/opentracing/opentracing-go"
+ jtracing "github.com/uber/jaeger-client-go"
+ jcfg "github.com/uber/jaeger-client-go/config"
+ "io"
+ "os"
+ "strings"
+ "sync"
+)
+
+const (
+ RootSpanNameKey = "op-name"
+)
+
+// Global Settings governing the Log Correlation and Tracing features. Should only
+// be updated through the exposed public methods
+type LogFeaturesManager struct {
+ isTracePublishingEnabled bool
+ isLogCorrelationEnabled bool
+ componentName string // Name of component extracted from ENV variable
+ activeTraceAgentAddress string
+ lock sync.Mutex
+}
+
+var globalLFM *LogFeaturesManager = &LogFeaturesManager{}
+
+func GetGlobalLFM() *LogFeaturesManager {
+ return globalLFM
+}
+
+// A Wrapper to utilize currently Active Tracer instance. The middleware library being used for generating
+// Spans for GRPC API calls does not support dynamically setting the Active Tracer similar to the SetGlobalTracer method
+// provided by OpenTracing API
+type ActiveTracerProxy struct {
+}
+
+func (atw ActiveTracerProxy) StartSpan(operationName string, opts ...opentracing.StartSpanOption) opentracing.Span {
+ return opentracing.GlobalTracer().StartSpan(operationName, opts...)
+}
+
+func (atw ActiveTracerProxy) Inject(sm opentracing.SpanContext, format interface{}, carrier interface{}) error {
+ return opentracing.GlobalTracer().Inject(sm, format, carrier)
+}
+
+func (atw ActiveTracerProxy) Extract(format interface{}, carrier interface{}) (opentracing.SpanContext, error) {
+ return opentracing.GlobalTracer().Extract(format, carrier)
+}
+
+// Jaeger complaint Logger instance to redirect logs to Default Logger
+type traceLogger struct {
+ logger *clogger
+}
+
+func (tl traceLogger) Error(msg string) {
+ tl.logger.Error(context.Background(), msg)
+}
+
+func (tl traceLogger) Infof(msg string, args ...interface{}) {
+ // Tracing logs should be performed only at Debug Verbosity
+ tl.logger.Debugf(context.Background(), msg, args...)
+}
+
+// Wrapper to handle correct Closer call at the time of Process Termination
+type traceCloser struct {
+}
+
+func (c traceCloser) Close() error {
+ currentActiveTracer := opentracing.GlobalTracer()
+ if currentActiveTracer != nil {
+ if jTracer, ok := currentActiveTracer.(*jtracing.Tracer); ok {
+ jTracer.Close()
+ }
+ }
+
+ return nil
+}
+
+// Method to Initialize Jaeger based Tracing client based on initial status of Tracing Publish and Log Correlation
+func (lfm *LogFeaturesManager) InitTracingAndLogCorrelation(tracePublishEnabled bool, traceAgentAddress string, logCorrelationEnabled bool) (io.Closer, error) {
+ lfm.componentName = os.Getenv("COMPONENT_NAME")
+ if lfm.componentName == "" {
+ return nil, errors.New("Unable to retrieve PoD Component Name from Runtime env")
+ }
+
+ lfm.lock.Lock()
+ defer lfm.lock.Unlock()
+
+ // Use NoopTracer when both Tracing Publishing and Log Correlation are disabled
+ if !tracePublishEnabled && !logCorrelationEnabled {
+ logger.Info(context.Background(), "Skipping Global Tracer initialization as both Trace publish and Log correlation are configured as disabled")
+ lfm.isTracePublishingEnabled = false
+ lfm.isLogCorrelationEnabled = false
+ opentracing.SetGlobalTracer(opentracing.NoopTracer{})
+ return traceCloser{}, nil
+ }
+
+ tracer, _, err := lfm.constructJaegerTracer(tracePublishEnabled, traceAgentAddress, true)
+ if err != nil {
+ return nil, err
+ }
+
+ // Initialize variables representing Active Status
+ opentracing.SetGlobalTracer(tracer)
+ lfm.isTracePublishingEnabled = tracePublishEnabled
+ lfm.activeTraceAgentAddress = traceAgentAddress
+ lfm.isLogCorrelationEnabled = logCorrelationEnabled
+ return traceCloser{}, nil
+}
+
+// Method to replace Active Tracer along with graceful closer of previous tracer
+func (lfm *LogFeaturesManager) replaceActiveTracer(tracer opentracing.Tracer) {
+ currentActiveTracer := opentracing.GlobalTracer()
+ opentracing.SetGlobalTracer(tracer)
+
+ if currentActiveTracer != nil {
+ if jTracer, ok := currentActiveTracer.(*jtracing.Tracer); ok {
+ jTracer.Close()
+ }
+ }
+}
+
+func (lfm *LogFeaturesManager) GetLogCorrelationStatus() bool {
+ lfm.lock.Lock()
+ defer lfm.lock.Unlock()
+
+ return lfm.isLogCorrelationEnabled
+}
+
+func (lfm *LogFeaturesManager) SetLogCorrelationStatus(isEnabled bool) {
+ lfm.lock.Lock()
+ defer lfm.lock.Unlock()
+
+ if isEnabled == lfm.isLogCorrelationEnabled {
+ logger.Debugf(context.Background(), "Ignoring Log Correlation Set operation with value %t; current Status same as desired", isEnabled)
+ return
+ }
+
+ if isEnabled {
+ // Construct new Tracer instance if Log Correlation has been enabled and current active tracer is a NoopTracer instance.
+ // Continue using the earlier tracer instance in case of any error
+ if _, ok := opentracing.GlobalTracer().(opentracing.NoopTracer); ok {
+ tracer, _, err := lfm.constructJaegerTracer(lfm.isTracePublishingEnabled, lfm.activeTraceAgentAddress, false)
+ if err != nil {
+ logger.Warnf(context.Background(), "Log Correlation Enable operation failed with error: %s", err.Error())
+ return
+ }
+
+ lfm.replaceActiveTracer(tracer)
+ }
+
+ lfm.isLogCorrelationEnabled = true
+ logger.Info(context.Background(), "Log Correlation has been enabled")
+
+ } else {
+ // Switch to NoopTracer when Log Correlation has been disabled and Tracing Publish is already disabled
+ if _, ok := opentracing.GlobalTracer().(opentracing.NoopTracer); !ok && !lfm.isTracePublishingEnabled {
+ lfm.replaceActiveTracer(opentracing.NoopTracer{})
+ }
+
+ lfm.isLogCorrelationEnabled = false
+ logger.Info(context.Background(), "Log Correlation has been disabled")
+ }
+}
+
+func (lfm *LogFeaturesManager) GetTracePublishingStatus() bool {
+ lfm.lock.Lock()
+ defer lfm.lock.Unlock()
+
+ return lfm.isTracePublishingEnabled
+}
+
+func (lfm *LogFeaturesManager) SetTracePublishingStatus(isEnabled bool) {
+ lfm.lock.Lock()
+ defer lfm.lock.Unlock()
+
+ if isEnabled == lfm.isTracePublishingEnabled {
+ logger.Debugf(context.Background(), "Ignoring Trace Publishing Set operation with value %t; current Status same as desired", isEnabled)
+ return
+ }
+
+ if isEnabled {
+ // Construct new Tracer instance if Tracing Publish has been enabled (even if a Jaeger instance is already active)
+ // This is needed to ensure that a fresh lookup of Jaeger Agent address is performed again while performing
+ // Disable-Enable of Tracing
+ tracer, _, err := lfm.constructJaegerTracer(isEnabled, lfm.activeTraceAgentAddress, false)
+ if err != nil {
+ logger.Warnf(context.Background(), "Trace Publishing Enable operation failed with error: %s", err.Error())
+ return
+ }
+ lfm.replaceActiveTracer(tracer)
+
+ lfm.isTracePublishingEnabled = true
+ logger.Info(context.Background(), "Tracing Publishing has been enabled")
+ } else {
+ // Switch to NoopTracer when Tracing Publish has been disabled and Log Correlation is already disabled
+ if !lfm.isLogCorrelationEnabled {
+ lfm.replaceActiveTracer(opentracing.NoopTracer{})
+ } else {
+ // Else construct a new Jaeger Instance with publishing disabled
+ tracer, _, err := lfm.constructJaegerTracer(isEnabled, lfm.activeTraceAgentAddress, false)
+ if err != nil {
+ logger.Warnf(context.Background(), "Trace Publishing Disable operation failed with error: %s", err.Error())
+ return
+ }
+ lfm.replaceActiveTracer(tracer)
+ }
+
+ lfm.isTracePublishingEnabled = false
+ logger.Info(context.Background(), "Tracing Publishing has been disabled")
+ }
+}
+
+// Method to contruct a new Jaeger Tracer instance based on given Trace Agent address and Publish status.
+// The last attribute indicates whether to use Loopback IP for creating Jaeger Client when the DNS lookup
+// of supplied Trace Agent address has failed. It is fine to fallback during the initialization step, but
+// not later (when enabling/disabling the status dynamically)
+func (lfm *LogFeaturesManager) constructJaegerTracer(tracePublishEnabled bool, traceAgentAddress string, fallbackToLoopbackAllowed bool) (opentracing.Tracer, io.Closer, error) {
+ cfg := jcfg.Configuration{ServiceName: lfm.componentName}
+
+ var err error
+ var jReporterConfig jcfg.ReporterConfig
+ var jReporterCfgOption jtracing.Reporter
+
+ logger.Info(context.Background(), "Constructing new Jaeger Tracer instance")
+
+ // Attempt Trace Agent Address first; will fallback to Loopback IP if it fails
+ jReporterConfig = jcfg.ReporterConfig{LocalAgentHostPort: traceAgentAddress, LogSpans: true}
+ jReporterCfgOption, err = jReporterConfig.NewReporter(lfm.componentName, jtracing.NewNullMetrics(), traceLogger{logger: logger.(*clogger)})
+
+ if err != nil {
+ if !fallbackToLoopbackAllowed {
+ return nil, nil, errors.New("Reporter Creation for given Trace Agent address " + traceAgentAddress + " failed with error : " + err.Error())
+ }
+
+ logger.Infow(context.Background(), "Unable to create Reporter with given Trace Agent address",
+ Fields{"error": err, "address": traceAgentAddress})
+ // The Reporter initialization may fail due to Invalid Agent address or non-existent Agent (DNS lookup failure).
+ // It is essential for Tracer Instance to still start for correct Span propagation needed for log correlation.
+ // Thus, falback to use loopback IP for Reporter initialization before throwing back any error
+ tracePublishEnabled = false
+
+ jReporterConfig.LocalAgentHostPort = "127.0.0.1:6831"
+ jReporterCfgOption, err = jReporterConfig.NewReporter(lfm.componentName, jtracing.NewNullMetrics(), traceLogger{logger: logger.(*clogger)})
+ if err != nil {
+ return nil, nil, errors.New("Failed to initialize Jaeger Tracing due to Reporter creation error : " + err.Error())
+ }
+ }
+
+ // To start with, we are using Constant Sampling type
+ samplerParam := 0 // 0: Do not publish span, 1: Publish
+ if tracePublishEnabled {
+ samplerParam = 1
+ }
+ jSamplerConfig := jcfg.SamplerConfig{Type: "const", Param: float64(samplerParam)}
+ jSamplerCfgOption, err := jSamplerConfig.NewSampler(lfm.componentName, jtracing.NewNullMetrics())
+ if err != nil {
+ return nil, nil, errors.New("Unable to create Sampler : " + err.Error())
+ }
+
+ return cfg.NewTracer(jcfg.Reporter(jReporterCfgOption), jcfg.Sampler(jSamplerCfgOption))
+}
+
+func TerminateTracing(c io.Closer) {
+ err := c.Close()
+ if err != nil {
+ logger.Error(context.Background(), "error-while-closing-jaeger-tracer", Fields{"err": err})
+ }
+}
+
+// Extracts details of Execution Context as log fields from the Tracing Span injected into the
+// context instance. Following log fields are extracted:
+// 1. Operation Name : key as 'op-name' and value as Span operation name
+// 2. Operation Id : key as 'op-id' and value as 64 bit Span Id in hex digits string
+//
+// Additionally, any tags present in Span are also extracted to use as log fields e.g. device-id.
+//
+// If no Span is found associated with context, blank slice is returned without any log fields
+func (lfm *LogFeaturesManager) ExtractContextAttributes(ctx context.Context) []interface{} {
+ if !lfm.isLogCorrelationEnabled {
+ return make([]interface{}, 0)
+ }
+
+ attrMap := make(map[string]interface{})
+
+ if ctx != nil {
+ if span := opentracing.SpanFromContext(ctx); span != nil {
+ if jspan, ok := span.(*jtracing.Span); ok {
+ // Add Log fields for operation identified by Root Level Span (Trace)
+ opId := fmt.Sprintf("%016x", jspan.SpanContext().TraceID().Low) // Using Sprintf to avoid removal of leading 0s
+ opName := jspan.BaggageItem(RootSpanNameKey)
+
+ taskId := fmt.Sprintf("%016x", uint64(jspan.SpanContext().SpanID())) // Using Sprintf to avoid removal of leading 0s
+ taskName := jspan.OperationName()
+
+ if opName == "" {
+ span.SetBaggageItem(RootSpanNameKey, taskName)
+ opName = taskName
+ }
+
+ attrMap["op-id"] = opId
+ attrMap["op-name"] = opName
+
+ // Add Log fields for task identified by Current Span, if it is different
+ // than operation
+ if taskId != opId {
+ attrMap["task-id"] = taskId
+ attrMap["task-name"] = taskName
+ }
+
+ for k, v := range jspan.Tags() {
+ // Ignore the special tags added by Jaeger, middleware (sampler.type, span.*) present in the span
+ if strings.HasPrefix(k, "sampler.") || strings.HasPrefix(k, "span.") || k == "component" {
+ continue
+ }
+
+ attrMap[k] = v
+ }
+
+ processBaggageItems := func(k, v string) bool {
+ if k != "rpc-span-name" {
+ attrMap[k] = v
+ }
+ return true
+ }
+
+ jspan.SpanContext().ForeachBaggageItem(processBaggageItems)
+ }
+ }
+ }
+
+ return serializeMap(attrMap)
+}
+
+// Method to inject additional log fields into Span e.g. device-id
+func EnrichSpan(ctx context.Context, keyAndValues ...Fields) {
+ span := opentracing.SpanFromContext(ctx)
+ if span != nil {
+ if jspan, ok := span.(*jtracing.Span); ok {
+ // Inject as a BaggageItem when the Span is the Root Span so that it propagates
+ // across the components along with Root Span (called as Trace)
+ // Else, inject as a Tag so that it is attached to the Child Task
+ isRootSpan := false
+ if jspan.SpanContext().TraceID().String() == jspan.SpanContext().SpanID().String() {
+ isRootSpan = true
+ }
+
+ for _, field := range keyAndValues {
+ for k, v := range field {
+ if isRootSpan {
+ span.SetBaggageItem(k, v.(string))
+ } else {
+ span.SetTag(k, v)
+ }
+ }
+ }
+ }
+ }
+}
+
+// Method to inject Error into the Span in event of any operation failure
+func MarkSpanError(ctx context.Context, err error) {
+ span := opentracing.SpanFromContext(ctx)
+ if span != nil {
+ span.SetTag("error", true)
+ span.SetTag("err", err)
+ }
+}
+
+// Creates a Child Span from Parent Span embedded in passed context. Should be used before starting a new major
+// operation in Synchronous or Asynchronous mode (go routine), such as following:
+// 1. Start of all implemented External API methods unless using a interceptor for auto-injection of Span (Server side impl)
+// 2. Just before calling an Third-Party lib which is invoking a External API (etcd, kafka)
+// 3. In start of a Go Routine responsible for performing a major task involving significant duration
+// 4. Any method which is suspected to be time consuming...
+func CreateChildSpan(ctx context.Context, taskName string, keyAndValues ...Fields) (opentracing.Span, context.Context) {
+ if !GetGlobalLFM().GetLogCorrelationStatus() && !GetGlobalLFM().GetTracePublishingStatus() {
+ return opentracing.NoopTracer{}.StartSpan(taskName), ctx
+ }
+
+ parentSpan := opentracing.SpanFromContext(ctx)
+ childSpan, newCtx := opentracing.StartSpanFromContext(ctx, taskName)
+
+ if parentSpan == nil || parentSpan.BaggageItem(RootSpanNameKey) == "" {
+ childSpan.SetBaggageItem(RootSpanNameKey, taskName)
+ }
+
+ EnrichSpan(newCtx, keyAndValues...)
+ return childSpan, newCtx
+}
+
+// Creates a Async Child Span with Follows-From relationship from Parent Span embedded in passed context.
+// Should be used only in scenarios when
+// a) There is dis-continuation in execution and thus result of Child span does not affect the Parent flow at all
+// b) The execution of Child Span is guaranteed to start after the completion of Parent Span
+// In case of any confusion, use CreateChildSpan method
+// Some situations where this method would be suitable includes Kafka Async RPC call, Propagation of Event across
+// a channel etc.
+func CreateAsyncSpan(ctx context.Context, taskName string, keyAndValues ...Fields) (opentracing.Span, context.Context) {
+ if !GetGlobalLFM().GetLogCorrelationStatus() && !GetGlobalLFM().GetTracePublishingStatus() {
+ return opentracing.NoopTracer{}.StartSpan(taskName), ctx
+ }
+
+ var asyncSpan opentracing.Span
+ var newCtx context.Context
+
+ parentSpan := opentracing.SpanFromContext(ctx)
+
+ // We should always be creating Aysnc span from a Valid parent span. If not, create a Child span instead
+ if parentSpan == nil {
+ logger.Warn(context.Background(), "Async span must be created with a Valid parent span only")
+ asyncSpan, newCtx = opentracing.StartSpanFromContext(ctx, taskName)
+ } else {
+ // Use Background context as the base for Follows-from case; else new span is getting both Child and FollowsFrom relationship
+ asyncSpan, newCtx = opentracing.StartSpanFromContext(context.Background(), taskName, opentracing.FollowsFrom(parentSpan.Context()))
+ }
+
+ if parentSpan == nil || parentSpan.BaggageItem(RootSpanNameKey) == "" {
+ asyncSpan.SetBaggageItem(RootSpanNameKey, taskName)
+ }
+
+ EnrichSpan(newCtx, keyAndValues...)
+ return asyncSpan, newCtx
+}
+
+// Extracts the span from Source context and injects into the supplied Target context.
+// This should be used in situations wherein we are calling a time-sensitive operation (etcd update) and hence
+// had a context.Background() used earlier to avoid any cancellation/timeout of operation by passed context.
+// This will allow propagation of span with a different base context (and not the original context)
+func WithSpanFromContext(targetCtx, sourceCtx context.Context) context.Context {
+ span := opentracing.SpanFromContext(sourceCtx)
+ return opentracing.ContextWithSpan(targetCtx, span)
+}
+
+// Utility method to convert log Fields into array of interfaces expected by zap logger methods
+func serializeMap(fields Fields) []interface{} {
+ data := make([]interface{}, len(fields)*2)
+ i := 0
+ for k, v := range fields {
+ data[i] = k
+ data[i+1] = v
+ i = i + 2
+ }
+ return data
+}