RIC:1060: Change in PTL
[com/golog.git] / mdclog.go
1 /*
2  *  Copyright (c) 2019 AT&T Intellectual Property.
3  *  Copyright (c) 2018-2019 Nokia.
4  *
5  *  Licensed under the Apache License, Version 2.0 (the "License");
6  *  you may not use this file except in compliance with the License.
7  *  You may obtain a copy of the License at
8  *
9  *     http://www.apache.org/licenses/LICENSE-2.0
10  *
11  *  Unless required by applicable law or agreed to in writing, software
12  *  distributed under the License is distributed on an "AS IS" BASIS,
13  *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14  *  See the License for the specific language governing permissions and
15  *  limitations under the License.
16  *
17  * This source code is part of the near-RT RIC (RAN Intelligent Controller)
18  * platform project (RICP).
19  */
20
21 // Package golog implements a simple structured logging with MDC (Mapped Diagnostics Context) support.
22 package golog
23
24 import (
25         "bytes"
26         "encoding/json"
27         "fmt"
28         "io"
29         "io/ioutil"
30         "k8s.io/utils/inotify"
31         "os"
32         "path/filepath"
33         "strconv"
34         "strings"
35         "sync"
36         "syscall"
37         "time"
38 )
39
40 // Level is a type define for the logging level.
41 type Level int
42
43 const (
44         // ERR is an error level log entry.
45         ERR Level = 1
46         // WARN is a warning level log entry.
47         WARN Level = 2
48         // INFO is an info level log entry.
49         INFO Level = 3
50         // DEBUG is a debug level log entry.
51         DEBUG Level = 4
52 )
53
54 // MdcLogger is the logger instance, created with InitLogger() function.
55 type MdcLogger struct {
56         proc      string
57         writer    io.Writer
58         mdc       map[string]string
59         mutex     sync.Mutex
60         level     Level
61         init_done int
62 }
63
64 type logEntry struct {
65         Ts   int64             `json:"ts"`
66         Crit string            `json:"crit"`
67         Id   string            `json:"id"`
68         Mdc  map[string]string `json:"mdc"`
69         Msg  string            `json:"msg"`
70 }
71
72 func levelString(level Level) string {
73         switch level {
74         case ERR:
75                 return "ERROR"
76         case WARN:
77                 return "WARNING"
78         case INFO:
79                 return "INFO"
80         case DEBUG:
81                 return "DEBUG"
82         default:
83                 return ""
84         }
85 }
86
87 func getTime() int64 {
88         ns := time.Time.UnixNano(time.Now())
89         return ns / int64(time.Millisecond)
90 }
91
92 func (l *MdcLogger) formatLog(level Level, msg string) ([]byte, error) {
93         log := logEntry{getTime(), levelString(level), l.proc, l.mdc, msg}
94         buf := bytes.NewBuffer(nil)
95         encoder := json.NewEncoder(buf)
96         encoder.SetEscapeHTML(false)
97         err := encoder.Encode(log)
98         return buf.Bytes(), err
99 }
100
101 func initLogger(proc string, writer io.Writer) (*MdcLogger, error) {
102         return &MdcLogger{proc: proc, writer: writer, mdc: make(map[string]string), level: DEBUG, init_done: 0}, nil
103 }
104
105 // InitLogger is the init routine which returns a new logger instance.
106 // The program identity is given as a parameter. The identity
107 // is added to every log writing.
108 // The function returns a new instance or an error.
109 func InitLogger(proc string) (*MdcLogger, error) {
110         return initLogger(proc, os.Stdout)
111 }
112
113 // Log is the basic logging function to write a log message with
114 // the given level
115 func (l *MdcLogger) Log(level Level, formatMsg string, a ...interface{}) {
116         l.mutex.Lock()
117         defer l.mutex.Unlock()
118         if l.level < level {
119                 return
120         }
121         log, err := l.formatLog(level, fmt.Sprintf(formatMsg, a...))
122         if err == nil {
123                 l.writer.Write(log)
124         }
125 }
126
127 // Error is the "error" level logging function.
128 func (l *MdcLogger) Error(formatMsg string, a ...interface{}) {
129         l.Log(ERR, formatMsg, a...)
130 }
131
132 // Warning is the "warning" level logging function.
133 func (l *MdcLogger) Warning(formatMsg string, a ...interface{}) {
134         l.Log(WARN, formatMsg, a...)
135 }
136
137 // Info is the "info" level logging function.
138 func (l *MdcLogger) Info(formatMsg string, a ...interface{}) {
139         l.Log(INFO, formatMsg, a...)
140 }
141
142 // Debug is the "debug" level logging function.
143 func (l *MdcLogger) Debug(formatMsg string, a ...interface{}) {
144         l.Log(DEBUG, formatMsg, a...)
145 }
146
147 // LevelSet sets the current logging level.
148 // Log writings with less significant level are discarded.
149 func (l *MdcLogger) LevelSet(level Level) {
150         l.level = level
151 }
152
153 // LevelGet returns the current logging level.
154 func (l *MdcLogger) LevelGet() Level {
155         return l.level
156 }
157
158 // MdcAdd adds a new MDC key value pair to the logger.
159 func (l *MdcLogger) MdcAdd(key string, value string) {
160         l.mutex.Lock()
161         defer l.mutex.Unlock()
162         l.mdc[key] = value
163 }
164
165 // MdcRemove removes an MDC key from the logger.
166 func (l *MdcLogger) MdcRemove(key string) {
167         l.mutex.Lock()
168         defer l.mutex.Unlock()
169         delete(l.mdc, key)
170 }
171
172 // MdcGet gets the value of an MDC from the logger.
173 // The function returns the value string and a boolean
174 // which tells if the key was found or not.
175 func (l *MdcLogger) MdcGet(key string) (string, bool) {
176         l.mutex.Lock()
177         defer l.mutex.Unlock()
178         val, ok := l.mdc[key]
179         return val, ok
180 }
181
182 // MdcClean removes all MDC keys from the logger.
183 func (l *MdcLogger) MdcClean() {
184         l.mutex.Lock()
185         defer l.mutex.Unlock()
186         l.mdc = make(map[string]string)
187 }
188
189 func (l *MdcLogger) MdcUpdate(key string, value string) {
190         _, ok := l.MdcGet(key)
191         if ok {
192                 l.MdcRemove(key)
193         }
194         l.MdcAdd(key, value)
195 }
196
197 func (l *MdcLogger) ParseFileContent(fileName string) {
198         data, err := ioutil.ReadFile(fileName)
199         if err != nil {
200                 fmt.Println("File reading error", err)
201                 return
202         }
203         for _, lineData := range strings.Split(string(data), "\n") {
204                 if strings.Contains(lineData, "log-level:") {
205                         var level = ERR
206                         strList := strings.Split(lineData, ":")
207                         if strings.Contains(strings.ToUpper(strList[1]), "DEBUG") {
208                                 level = DEBUG
209                         } else if strings.Contains(strings.ToUpper(strList[1]), "INFO") {
210                                 level = INFO
211                         } else if strings.Contains(strings.ToUpper(strList[1]), "ERR") {
212                                 level = ERR
213                         } else if strings.Contains(strings.ToUpper(strList[1]), "WARN") {
214                                 level = WARN
215                         }
216                         l.LevelSet(level)
217                 }
218         }
219 }
220
221 func (l *MdcLogger) watch_changes(watcher *inotify.Watcher, fileName string) {
222         for {
223                 select {
224                 case ev := <-watcher.Event:
225                         if strings.Contains(ev.Name, filepath.Dir(fileName)) {
226                                 l.ParseFileContent(fileName)
227                         }
228                 case err := <-watcher.Error:
229                         fmt.Println("error:", err)
230                 }
231         }
232 }
233
234 func (l *MdcLogger) readEnvVar(envKey string) string {
235         envValue, provided := os.LookupEnv(envKey)
236         if !provided {
237                 envValue = ""
238         }
239         return envValue
240 }
241
242 func (l *MdcLogger) Mdclog_format_initialize(logFileMonitor int) int {
243         ret := -1
244         logFields := []string{"SYSTEM_NAME", "HOST_NAME", "SERVICE_NAME", "CONTAINER_NAME", "POD_NAME"}
245         for _, envKey := range logFields {
246                 envValue := l.readEnvVar(envKey)
247                 l.MdcUpdate(envKey, envValue)
248         }
249         l.MdcUpdate("PID", strconv.Itoa(os.Getpid()))
250         if logFileMonitor > 0 {
251                 watchPath := l.readEnvVar("CONFIG_MAP_NAME")
252                 _, err := os.Stat(watchPath)
253                 if !os.IsNotExist(err) {
254                         if l.init_done == 0 {
255                                 l.mutex.Lock()
256                                 l.init_done = 1
257                                 l.mutex.Unlock()
258                                 watcher, err := inotify.NewWatcher()
259                                 if err != nil {
260                                         return -1
261                                 }
262                                 err = watcher.AddWatch(filepath.Dir(watchPath), syscall.IN_CLOSE_WRITE|syscall.IN_CREATE|syscall.IN_CLOSE)
263                                 if err != nil {
264                                         return -1
265                                 }
266                                 l.ParseFileContent(watchPath)
267                                 go l.watch_changes(watcher, watchPath)
268                                 ret = 0
269                         }
270                 }
271         }
272         return ret
273 }