blob: b45c2c814ef288712710c1afa0711e313c953fb7 [file] [log] [blame]
divyadesai81bb7ba2020-03-11 11:45:23 +00001/*
2 * Copyright 2018-present Open Networking Foundation
3
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7
8 * http://www.apache.org/licenses/LICENSE-2.0
9
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17// Package Config provides dynamic logging configuration for specific Voltha component type implemented using backend.The package can be used in following manner
18// Any Voltha component type can start dynamic logging by starting goroutine of ProcessLogConfigChange after starting kvClient for the component.
19
20package config
21
22import (
23 "context"
24 "crypto/md5"
25 "encoding/json"
26 "errors"
27 "github.com/opencord/voltha-lib-go/v3/pkg/log"
28 "os"
29 "strings"
30)
31
32// ComponentLogController represents a Configuration for Logging Config of specific Voltha component type
33// It stores ComponentConfig and GlobalConfig of loglevel config of specific Voltha component type
34// For example,ComponentLogController instance will be created for rw-core component
35type ComponentLogController struct {
36 ComponentName string
37 componentNameConfig *ComponentConfig
38 GlobalConfig *ComponentConfig
39 configManager *ConfigManager
40 logHash [16]byte
41}
42
43func NewComponentLogController(cm *ConfigManager) (*ComponentLogController, error) {
44
45 log.Debug("creating-new-component-log-controller")
46 componentName := os.Getenv("COMPONENT_NAME")
47 if componentName == "" {
48 return nil, errors.New("Unable to retrieve PoD Component Name from Runtime env")
49 }
50
51 return &ComponentLogController{
52 ComponentName: componentName,
53 componentNameConfig: nil,
54 GlobalConfig: nil,
55 configManager: cm,
56 }, nil
57
58}
59
60// ProcessLogConfigChange initialize component config and global config
61func ProcessLogConfigChange(cm *ConfigManager, ctx context.Context) {
62 cc, err := NewComponentLogController(cm)
63 if err != nil {
64 log.Errorw("unable-to-construct-component-log-controller-instance-for-log-config-monitoring", log.Fields{"error": err})
65 return
66 }
67
68 log.Debugw("processing-log-config-change", log.Fields{"cc": cc})
69
70 cc.GlobalConfig = cm.InitComponentConfig("global", ConfigTypeLogLevel)
71 log.Debugw("global-log-config", log.Fields{"cc-global-config": cc.GlobalConfig})
72
73 cc.componentNameConfig = cm.InitComponentConfig(cc.ComponentName, ConfigTypeLogLevel)
74 log.Debugw("component-log-config", log.Fields{"cc-component-name-config": cc.componentNameConfig})
75
76 cc.processLogConfig(ctx)
77}
78
79// ProcessLogConfig wait on componentn config and global config channel for any changes
80// Event channel will be recieved from backend for valid change type
81// Then data for componentn log config and global log config will be retrieved from backend and stored in updatedLogConfig in precedence order
82// If any changes in updatedLogConfig will be applied on component
83func (c *ComponentLogController) processLogConfig(ctx context.Context) {
84
85 componentConfigEventChan := c.componentNameConfig.MonitorForConfigChange(ctx)
86
87 globalConfigEventChan := c.GlobalConfig.MonitorForConfigChange(ctx)
88
89 // process the events for componentName and global config
90 var configEvent *ConfigChangeEvent
91 for {
92 select {
93 case configEvent = <-globalConfigEventChan:
94 case configEvent = <-componentConfigEventChan:
95
96 }
97 log.Debugw("processing-log-config-change", log.Fields{"config-event": configEvent})
98
99 updatedLogConfig, err := c.buildUpdatedLogConfig(ctx)
100 if err != nil {
101 log.Warnw("unable-to-fetch-updated-log-config", log.Fields{"error": err})
102 continue
103 }
104
105 log.Debugw("applying-updated-log-config", log.Fields{"updated-log-config": updatedLogConfig})
106
107 if err := c.loadAndApplyLogConfig(updatedLogConfig); err != nil {
108 log.Warnw("unable-to-load-and-apply-log-config", log.Fields{"error": err})
109 }
110 }
111
112}
113
114// get active loglevel from the zap logger
115func getActiveLogLevel() map[string]string {
116 loglevel := make(map[string]string)
117
118 // now do the default log level
119 if level, err := log.LogLevelToString(log.GetDefaultLogLevel()); err == nil {
120 loglevel["default"] = level
121 }
122
123 // do the per-package log levels
124 for _, packageName := range log.GetPackageNames() {
125 level, err := log.GetPackageLogLevel(packageName)
126 if err != nil {
127 log.Warnw("unable-to-fetch-current-active-loglevel-for-package-name", log.Fields{"package-name": packageName, "error": err})
128 }
129
130 packagename := strings.ReplaceAll(packageName, "/", "#")
131 if l, err := log.LogLevelToString(level); err == nil {
132 loglevel[packagename] = l
133 }
134
135 }
136 log.Debugw("getting-log-levels-from-zap-logger", log.Fields{"log-level": loglevel})
137
138 return loglevel
139}
140
141func (c *ComponentLogController) getGlobalLogConfig(ctx context.Context) (string, error) {
142
143 globalDefaultLogLevel := ""
144 globalLogConfig, err := c.GlobalConfig.RetrieveAll(ctx)
145 if err != nil {
146 return "", err
147 }
148
149 if globalLevel, ok := globalLogConfig["default"]; ok {
150 if _, err := log.StringToLogLevel(globalLevel); err != nil {
151 log.Warnw("unsupported-loglevel-config-defined-at-global-context-pacakge-name", log.Fields{"log-level": globalLevel})
152 } else {
153 globalDefaultLogLevel = globalLevel
154 }
155 }
156 log.Debugw("retrieved-global-log-config", log.Fields{"global-log-config": globalLogConfig})
157
158 return globalDefaultLogLevel, nil
159}
160
161func (c *ComponentLogController) getComponentLogConfig(globalDefaultLogLevel string, ctx context.Context) (map[string]string, error) {
162 var defaultPresent bool
163 componentLogConfig, err := c.componentNameConfig.RetrieveAll(ctx)
164 if err != nil {
165 return nil, err
166 }
167
168 for componentKey, componentLevel := range componentLogConfig {
169 if _, err := log.StringToLogLevel(componentLevel); err != nil || componentKey == "" {
170 log.Warnw("unsupported-loglevel-config-defined-at-component-context", log.Fields{"package-name": componentKey, "log-level": componentLevel})
171 delete(componentLogConfig, componentKey)
172 } else {
173 if componentKey == "default" {
174 defaultPresent = true
175 }
176 }
177 }
178 if !defaultPresent {
179 if globalDefaultLogLevel != "" {
180 componentLogConfig["default"] = globalDefaultLogLevel
181 }
182 }
183 log.Debugw("retrieved-component-log-config", log.Fields{"component-log-level": componentLogConfig})
184
185 return componentLogConfig, nil
186}
187
188// buildUpdatedLogConfig retrieve the global logConfig and component logConfig from backend
189// component logConfig stores the log config with precedence order
190// For example, If the global logConfig is set and component logConfig is set only for specific package then
191// component logConfig is stored with global logConfig and component logConfig of specific package
192// For example, If the global logConfig is set and component logConfig is set for specific package and as well as for default then
193// component logConfig is stored with component logConfig data only
194func (c *ComponentLogController) buildUpdatedLogConfig(ctx context.Context) (map[string]string, error) {
195 globalLogLevel, err := c.getGlobalLogConfig(ctx)
196 if err != nil {
197 return nil, err
198 }
199
200 componentLogConfig, err := c.getComponentLogConfig(globalLogLevel, ctx)
201 if err != nil {
202 return nil, err
203 }
204
205 log.Debugw("building-and-updating-log-config", log.Fields{"component-log-config": componentLogConfig})
206 return componentLogConfig, nil
207}
208
209// load and apply the current configuration for component name
210// create hash of loaded configuration using GenerateLogConfigHash
211// if there is previous hash stored, compare the hash to stored hash
212// if there is any change will call UpdateLogLevels
213func (c *ComponentLogController) loadAndApplyLogConfig(logConfig map[string]string) error {
214 currentLogHash, err := GenerateLogConfigHash(logConfig)
215 if err != nil {
216 return err
217 }
218
219 log.Debugw("loading-and-applying-log-config", log.Fields{"log-config": logConfig})
220 if c.logHash != currentLogHash {
221 UpdateLogLevels(logConfig)
222 c.logHash = currentLogHash
223 }
224 return nil
225}
226
227// getDefaultLogLevel to return active default log level
228func getDefaultLogLevel(logConfig map[string]string) string {
229
230 for key, level := range logConfig {
231 if key == "default" {
232 return level
233 }
234 }
235 return ""
236}
237
238// createCurrentLogLevel loop through the activeLogLevels recieved from zap logger and updatedLogLevels recieved from buildUpdatedLogConfig
239// The packageName is present or not will be checked in updatedLogLevels ,if the package name is not present then updatedLogLevels will be updated with
240// the packageName and loglevel with default log level
241func createCurrentLogLevel(activeLogLevels, updatedLogLevels map[string]string) map[string]string {
242 level := getDefaultLogLevel(updatedLogLevels)
243 for activeKey, activeLevel := range activeLogLevels {
244 if _, exist := updatedLogLevels[activeKey]; !exist {
245 if level != "" {
246 activeLevel = level
247 }
248 updatedLogLevels[activeKey] = activeLevel
249 }
250 }
251 return updatedLogLevels
252}
253
254// updateLogLevels update the loglevels for the component
255// retrieve active confguration from logger
256// compare with entries one by one and apply
257func UpdateLogLevels(logLevel map[string]string) {
258
259 activeLogLevels := getActiveLogLevel()
260 currentLogLevel := createCurrentLogLevel(activeLogLevels, logLevel)
261 for key, level := range currentLogLevel {
262 if key == "default" {
263 if l, err := log.StringToLogLevel(level); err == nil {
264 log.SetDefaultLogLevel(l)
265 }
266 } else {
267 pname := strings.ReplaceAll(key, "#", "/")
268 if _, err := log.AddPackage(log.JSON, log.DebugLevel, nil, pname); err != nil {
269 log.Warnw("unable-to-add-log-package", log.Fields{"package-name": pname, "error": err})
270 }
271 if l, err := log.StringToLogLevel(level); err == nil {
272 log.SetPackageLogLevel(pname, l)
273 }
274 }
275 }
276 log.Debugw("updated-log-level", log.Fields{"current-log-level": currentLogLevel})
277}
278
279// generate md5 hash of key value pairs appended into a single string
280// in order by key name
281func GenerateLogConfigHash(createHashLog map[string]string) ([16]byte, error) {
282 createHashLogBytes := []byte{}
283 levelData, err := json.Marshal(createHashLog)
284 if err != nil {
285 return [16]byte{}, err
286 }
287 createHashLogBytes = append(createHashLogBytes, levelData...)
288 return md5.Sum(createHashLogBytes), nil
289}