Add log formatting option and dynamic Log-Level change Implement MDC Log entry format... 64/4964/2 cherry dawn e-release f-release g-release v0.0.2
authorwahidw <abdulwahid.w@nokia.com>
Mon, 2 Nov 2020 05:10:18 +0000 (05:10 +0000)
committerwahidw <abdulwahid.w@nokia.com>
Tue, 3 Nov 2020 04:55:15 +0000 (04:55 +0000)
Signed-off-by: wahidw <abdulwahid.w@nokia.com>
Change-Id: If37a78ab2c4a8fb27a931087a9a7840827cc7d47

README.md
cmd/logtester/main.go
docs/developer-guide.rst
mdclog.go
mdclog_test.go

index 452c4f4..eb94143 100644 (file)
--- a/README.md
+++ b/README.md
@@ -1,4 +1,4 @@
-Logging library with MDC support
+Logging Library with MDC support
 ================================
 
 A Golang implementation of a structured logging library with Mapped Diagnostics Context (MDC) support.
index c97c604..8151f21 100644 (file)
@@ -24,14 +24,18 @@ import (
        "fmt"
        "os"
        "time"
-
        mdcloggo "gerrit.o-ran-sc.org/r/com/golog"
 )
 
 func main() {
        logger, _ := mdcloggo.InitLogger("myname")
+       logFileMonitor := 0
        logger.MdcAdd("foo", "bar")
        logger.MdcAdd("foo2", "bar2")
+       if logger.Mdclog_format_initialize(logFileMonitor) != 0 {
+               logger.Error("Failed in MDC Log Format Initialize")
+       }
+
        start := time.Now()
        for i := 0; i < 10; i++ {
                logger.Info("Some test logs")
index 072c0ac..191d833 100644 (file)
@@ -71,6 +71,8 @@ Currently the library only supports JSON formatted output written to standard ou
 \r
 `{"ts":1551183682974,"crit":"INFO","id":"myprog","mdc":{"second key":"other value","mykey":"keyval"},"msg":"hello world!"}`\r
 \r
+ `{"ts":1602081593063,"crit":"INFO","id":"myapp","mdc":{"PID":"21587","POD_NAME":"tta-app-5565fc4d6f-ppfl8","CONTAINER_NAME":"tta-app","SERVICE_NAME":"TEST_APP","HOST_NAME":"master-an","SYSTEM_NAME":"CloudSpace-0"},"msg":"This is an example log"}`\r
+\r
 Example\r
 -------\r
 \r
@@ -83,7 +85,11 @@ Example
  )\r
 \r
  func main() {\r
+    logFileMonitor := 0;\r
     logger, _ := mdcloggo.InitLogger("myname")\r
+    if(logger.Mdclog_format_initialize(logFileMonitor)!=0) {\r
+        logger.Error("UnSuccessful Format Initialize")\r
+    }\r
     logger.MdcAdd("mykey", "keyval")\r
     logger.Info("Some test logs")\r
  } \r
@@ -143,3 +149,15 @@ Description: The function returns the value string and a boolean which tells if
 .. code:: bash\r
 \r
  func (l *MdcLogger) MdcClean()\r
+\r
+7. Mdclog_format_initialize Adds the MDC log format with HostName, PodName, ContainerName, ServiceName,PID,CallbackNotifyforLogFieldChange\r
+\r
+.. code:: bash\r
+\r
+ func (l *MdcLogger) Mdclog_format_initialize(log_change_monitor int) (int)\r
+\r
+Description:  This api Initialzes mdclog print format using MDC Array by extracting the environment variables in the calling process for "SYSTEM_NAME", "HOST_NAME", "SERVICE_NAME", "CONTAINER_NAME", "POD_NAME" & "CONFIG_MAP_NAME"  mapped to HostName, ServiceName, ContainerName, Podname and Configuration-file-name of the services respectively.\r
+\r
+  Note: In K8s/Docker Containers the environment variables are declared in the Helm charts.\r
+\r
+  Refer xAPP developer guide for more information about how to define Helm chart.\r
index 6e6e295..ae28c17 100644 (file)
--- a/mdclog.go
+++ b/mdclog.go
@@ -26,8 +26,14 @@ import (
        "encoding/json"
        "fmt"
        "io"
+       "io/ioutil"
+       "k8s.io/utils/inotify"
        "os"
+       "path/filepath"
+       "strconv"
+       "strings"
        "sync"
+       "syscall"
        "time"
 )
 
@@ -47,11 +53,12 @@ const (
 
 // MdcLogger is the logger instance, created with InitLogger() function.
 type MdcLogger struct {
-       proc   string
-       writer io.Writer
-       mdc    map[string]string
-       mutex  sync.Mutex
-       level  Level
+       proc      string
+       writer    io.Writer
+       mdc       map[string]string
+       mutex     sync.Mutex
+       level     Level
+       init_done int
 }
 
 type logEntry struct {
@@ -92,7 +99,7 @@ func (l *MdcLogger) formatLog(level Level, msg string) ([]byte, error) {
 }
 
 func initLogger(proc string, writer io.Writer) (*MdcLogger, error) {
-       return &MdcLogger{proc: proc, writer: writer, mdc: make(map[string]string), level: DEBUG}, nil
+       return &MdcLogger{proc: proc, writer: writer, mdc: make(map[string]string), level: DEBUG, init_done: 0}, nil
 }
 
 // InitLogger is the init routine which returns a new logger instance.
@@ -178,3 +185,89 @@ func (l *MdcLogger) MdcClean() {
        defer l.mutex.Unlock()
        l.mdc = make(map[string]string)
 }
+
+func (l *MdcLogger) MdcUpdate(key string, value string) {
+       _, ok := l.MdcGet(key)
+       if ok {
+               l.MdcRemove(key)
+       }
+       l.MdcAdd(key, value)
+}
+
+func (l *MdcLogger) ParseFileContent(fileName string) {
+       data, err := ioutil.ReadFile(fileName)
+       if err != nil {
+               fmt.Println("File reading error", err)
+               return
+       }
+       for _, lineData := range strings.Split(string(data), "\n") {
+               if strings.Contains(lineData, "log-level:") {
+                       var level = ERR
+                       strList := strings.Split(lineData, ":")
+                       if strings.Contains(strings.ToUpper(strList[1]), "DEBUG") {
+                               level = DEBUG
+                       } else if strings.Contains(strings.ToUpper(strList[1]), "INFO") {
+                               level = INFO
+                       } else if strings.Contains(strings.ToUpper(strList[1]), "ERR") {
+                               level = ERR
+                       } else if strings.Contains(strings.ToUpper(strList[1]), "WARN") {
+                               level = WARN
+                       }
+                       l.LevelSet(level)
+               }
+       }
+}
+
+func (l *MdcLogger) watch_changes(watcher *inotify.Watcher, fileName string) {
+       for {
+               select {
+               case ev := <-watcher.Event:
+                       if strings.Contains(ev.Name, filepath.Dir(fileName)) {
+                               l.ParseFileContent(fileName)
+                       }
+               case err := <-watcher.Error:
+                       fmt.Println("error:", err)
+               }
+       }
+}
+
+func (l *MdcLogger) readEnvVar(envKey string) string {
+       envValue, provided := os.LookupEnv(envKey)
+       if !provided {
+               envValue = ""
+       }
+       return envValue
+}
+
+func (l *MdcLogger) Mdclog_format_initialize(logFileMonitor int) int {
+       ret := -1
+       logFields := []string{"SYSTEM_NAME", "HOST_NAME", "SERVICE_NAME", "CONTAINER_NAME", "POD_NAME"}
+       for _, envKey := range logFields {
+               envValue := l.readEnvVar(envKey)
+               l.MdcUpdate(envKey, envValue)
+       }
+       l.MdcUpdate("PID", strconv.Itoa(os.Getpid()))
+       if logFileMonitor > 0 {
+               watchPath := l.readEnvVar("CONFIG_MAP_NAME")
+               _, err := os.Stat(watchPath)
+               if !os.IsNotExist(err) {
+                       if l.init_done == 0 {
+                               l.mutex.Lock()
+                               l.init_done = 1
+                               l.mutex.Unlock()
+                               watcher, err := inotify.NewWatcher()
+                               if err != nil {
+                                       return -1
+                               }
+                               err = watcher.AddWatch(filepath.Dir(watchPath), syscall.IN_CLOSE_WRITE|syscall.IN_CREATE|syscall.IN_CLOSE)
+                               if err != nil {
+                                       return -1
+                               }
+                               l.ParseFileContent(watchPath)
+                               go l.watch_changes(watcher, watchPath)
+                               ret = 0
+                       }
+               }
+       }
+       return ret
+}
index 7b46611..8325e69 100644 (file)
@@ -23,6 +23,8 @@ package golog
 import (
        "bytes"
        "encoding/json"
+       "io/ioutil"
+       "os"
        "testing"
 
        "github.com/stretchr/testify/assert"
@@ -155,3 +157,40 @@ func TestDebugLogIsNotWrittenIfCurrentLevelIsInfo(t *testing.T) {
        logger.Debug("fooo")
        assert.Empty(t, logbuffer.String())
 }
+
+func TestLogFormatWithMdcArray(t *testing.T) {
+       logger, _ := InitLogger("app")
+       logFileMonitor := 0
+       logger.Mdclog_format_initialize(logFileMonitor)
+       logstr, err := logger.formatLog(INFO, "test")
+       assert.Nil(t, err, "formatLog fails")
+       v := make(map[string]interface{})
+       err = json.Unmarshal(logstr, &v)
+       assert.Equal(t, "INFO", v["crit"])
+       assert.Equal(t, "test", v["msg"])
+       assert.Equal(t, "app", v["id"])
+       _, ok := logger.MdcGet("SYSTEM_NAME")
+       assert.True(t, ok)
+       _, ok = logger.MdcGet("HOST_NAME")
+       assert.True(t, ok)
+       _, ok = logger.MdcGet("SERVICE_NAME")
+       assert.True(t, ok)
+       _, ok = logger.MdcGet("CONTAINER_NAME")
+       assert.True(t, ok)
+       _, ok = logger.MdcGet("POD_NAME")
+       assert.True(t, ok)
+}
+
+func TestLogLevelConfigFileParse(t *testing.T) {
+       logger, _ := InitLogger("app")
+       d1 := []byte("log-level:WARN\n\n")
+       err := ioutil.WriteFile("/tmp/log-file", d1, 0644)
+       assert.Nil(t, err, "Failed to create tmp log-file")
+       os.Setenv("CONFIG_MAP_NAME", "/tmp/log-file")
+       logFileMonitor := 1
+       logger.Mdclog_format_initialize(logFileMonitor)
+       assert.Equal(t, WARN, logger.LevelGet())
+       _, ok := logger.MdcGet("PID")
+       assert.True(t, ok)
+       logger.Mdclog_format_initialize(logFileMonitor)
+}