From fa620f44ece637013e7e11f64847761dda720dda Mon Sep 17 00:00:00 2001 From: wahidw Date: Mon, 2 Nov 2020 05:10:18 +0000 Subject: [PATCH] Add log formatting option and dynamic Log-Level change Implement MDC Log entry formatting options via environment variables and implement dynamic Log-Level change what makes possible to change the log level of a running process in a Docker container. Signed-off-by: wahidw Change-Id: If37a78ab2c4a8fb27a931087a9a7840827cc7d47 --- README.md | 2 +- cmd/logtester/main.go | 6 ++- docs/developer-guide.rst | 18 ++++++++ mdclog.go | 105 ++++++++++++++++++++++++++++++++++++++++++++--- mdclog_test.go | 39 ++++++++++++++++++ 5 files changed, 162 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 452c4f4..eb94143 100644 --- 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. diff --git a/cmd/logtester/main.go b/cmd/logtester/main.go index c97c604..8151f21 100644 --- a/cmd/logtester/main.go +++ b/cmd/logtester/main.go @@ -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") diff --git a/docs/developer-guide.rst b/docs/developer-guide.rst index 072c0ac..191d833 100644 --- a/docs/developer-guide.rst +++ b/docs/developer-guide.rst @@ -71,6 +71,8 @@ Currently the library only supports JSON formatted output written to standard ou `{"ts":1551183682974,"crit":"INFO","id":"myprog","mdc":{"second key":"other value","mykey":"keyval"},"msg":"hello world!"}` + `{"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"}` + Example ------- @@ -83,7 +85,11 @@ Example ) func main() { + logFileMonitor := 0; logger, _ := mdcloggo.InitLogger("myname") + if(logger.Mdclog_format_initialize(logFileMonitor)!=0) { + logger.Error("UnSuccessful Format Initialize") + } logger.MdcAdd("mykey", "keyval") logger.Info("Some test logs") } @@ -143,3 +149,15 @@ Description: The function returns the value string and a boolean which tells if .. code:: bash func (l *MdcLogger) MdcClean() + +7. Mdclog_format_initialize Adds the MDC log format with HostName, PodName, ContainerName, ServiceName,PID,CallbackNotifyforLogFieldChange + +.. code:: bash + + func (l *MdcLogger) Mdclog_format_initialize(log_change_monitor int) (int) + +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. + + Note: In K8s/Docker Containers the environment variables are declared in the Helm charts. + + Refer xAPP developer guide for more information about how to define Helm chart. diff --git a/mdclog.go b/mdclog.go index 6e6e295..ae28c17 100644 --- 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 +} diff --git a/mdclog_test.go b/mdclog_test.go index 7b46611..8325e69 100644 --- a/mdclog_test.go +++ b/mdclog_test.go @@ -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) +} -- 2.16.6