blob: 5579e049a4c088ae65f0f62550896261130db29a [file] [log] [blame]
divyadesai8bf96862020-02-07 12:24:26 +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
Girish Kumar21972fb2020-03-13 13:27:47 +000032const (
33 defaultLogLevelKey = "default" // kvstore key containing default loglevel
34 globalConfigRootNode = "global" // Root Node in kvstore containing global config
35 // that is applicable; unless overridden by individual component
36)
37
divyadesai8bf96862020-02-07 12:24:26 +000038// ComponentLogController represents a Configuration for Logging Config of specific Voltha component type
39// It stores ComponentConfig and GlobalConfig of loglevel config of specific Voltha component type
40// For example,ComponentLogController instance will be created for rw-core component
41type ComponentLogController struct {
42 ComponentName string
43 componentNameConfig *ComponentConfig
44 GlobalConfig *ComponentConfig
45 configManager *ConfigManager
46 logHash [16]byte
Girish Kumar21972fb2020-03-13 13:27:47 +000047 initialLogLevel string // Initial default log level set by helm chart
divyadesai8bf96862020-02-07 12:24:26 +000048}
49
50func NewComponentLogController(cm *ConfigManager) (*ComponentLogController, error) {
51
52 log.Debug("creating-new-component-log-controller")
53 componentName := os.Getenv("COMPONENT_NAME")
54 if componentName == "" {
55 return nil, errors.New("Unable to retrieve PoD Component Name from Runtime env")
56 }
57
Girish Kumar21972fb2020-03-13 13:27:47 +000058 var defaultLogLevel string
59 var err error
60 // Retrieve and save default log level; used for fallback if all loglevel config is cleared in etcd
61 if defaultLogLevel, err = log.LogLevelToString(log.GetDefaultLogLevel()); err != nil {
62 defaultLogLevel = "DEBUG"
63 }
64
divyadesai8bf96862020-02-07 12:24:26 +000065 return &ComponentLogController{
66 ComponentName: componentName,
67 componentNameConfig: nil,
68 GlobalConfig: nil,
69 configManager: cm,
Girish Kumar21972fb2020-03-13 13:27:47 +000070 initialLogLevel: defaultLogLevel,
divyadesai8bf96862020-02-07 12:24:26 +000071 }, nil
72
73}
74
75// ProcessLogConfigChange initialize component config and global config
76func ProcessLogConfigChange(cm *ConfigManager, ctx context.Context) {
77 cc, err := NewComponentLogController(cm)
78 if err != nil {
79 log.Errorw("unable-to-construct-component-log-controller-instance-for-log-config-monitoring", log.Fields{"error": err})
80 return
81 }
82
Girish Kumar21972fb2020-03-13 13:27:47 +000083 cc.GlobalConfig = cm.InitComponentConfig(globalConfigRootNode, ConfigTypeLogLevel)
divyadesai8bf96862020-02-07 12:24:26 +000084 log.Debugw("global-log-config", log.Fields{"cc-global-config": cc.GlobalConfig})
85
86 cc.componentNameConfig = cm.InitComponentConfig(cc.ComponentName, ConfigTypeLogLevel)
87 log.Debugw("component-log-config", log.Fields{"cc-component-name-config": cc.componentNameConfig})
88
89 cc.processLogConfig(ctx)
90}
91
Girish Kumar21972fb2020-03-13 13:27:47 +000092// ProcessLogConfig will first load and apply log config and then start waiting on component config and global config
93// channels for any changes. Event channel will be recieved from backend for valid change type
divyadesai8bf96862020-02-07 12:24:26 +000094// Then data for componentn log config and global log config will be retrieved from backend and stored in updatedLogConfig in precedence order
95// If any changes in updatedLogConfig will be applied on component
96func (c *ComponentLogController) processLogConfig(ctx context.Context) {
97
Girish Kumar21972fb2020-03-13 13:27:47 +000098 // Load and apply Log Config for first time
99 initialLogConfig, err := c.buildUpdatedLogConfig(ctx)
100 if err != nil {
101 log.Warnw("unable-to-load-log-config-at-startup", log.Fields{"error": err})
102 } else {
103 if err := c.loadAndApplyLogConfig(initialLogConfig); err != nil {
104 log.Warnw("unable-to-apply-log-config-at-startup", log.Fields{"error": err})
105 }
106 }
107
divyadesai8bf96862020-02-07 12:24:26 +0000108 componentConfigEventChan := c.componentNameConfig.MonitorForConfigChange(ctx)
109
110 globalConfigEventChan := c.GlobalConfig.MonitorForConfigChange(ctx)
111
112 // process the events for componentName and global config
113 var configEvent *ConfigChangeEvent
114 for {
115 select {
116 case configEvent = <-globalConfigEventChan:
117 case configEvent = <-componentConfigEventChan:
118
119 }
Girish Kumar21972fb2020-03-13 13:27:47 +0000120 log.Debugw("processing-log-config-change", log.Fields{"ChangeType": configEvent.ChangeType, "Package": configEvent.ConfigAttribute})
divyadesai8bf96862020-02-07 12:24:26 +0000121
122 updatedLogConfig, err := c.buildUpdatedLogConfig(ctx)
123 if err != nil {
124 log.Warnw("unable-to-fetch-updated-log-config", log.Fields{"error": err})
125 continue
126 }
127
128 log.Debugw("applying-updated-log-config", log.Fields{"updated-log-config": updatedLogConfig})
129
130 if err := c.loadAndApplyLogConfig(updatedLogConfig); err != nil {
131 log.Warnw("unable-to-load-and-apply-log-config", log.Fields{"error": err})
132 }
133 }
134
135}
136
137// get active loglevel from the zap logger
Girish Kumar21972fb2020-03-13 13:27:47 +0000138func getActiveLogLevels() map[string]string {
139 loglevels := make(map[string]string)
divyadesai8bf96862020-02-07 12:24:26 +0000140
141 // now do the default log level
142 if level, err := log.LogLevelToString(log.GetDefaultLogLevel()); err == nil {
Girish Kumar21972fb2020-03-13 13:27:47 +0000143 loglevels[defaultLogLevelKey] = level
divyadesai8bf96862020-02-07 12:24:26 +0000144 }
145
146 // do the per-package log levels
147 for _, packageName := range log.GetPackageNames() {
148 level, err := log.GetPackageLogLevel(packageName)
149 if err != nil {
150 log.Warnw("unable-to-fetch-current-active-loglevel-for-package-name", log.Fields{"package-name": packageName, "error": err})
Girish Kumar21972fb2020-03-13 13:27:47 +0000151 continue
divyadesai8bf96862020-02-07 12:24:26 +0000152 }
153
divyadesai8bf96862020-02-07 12:24:26 +0000154 if l, err := log.LogLevelToString(level); err == nil {
Girish Kumar21972fb2020-03-13 13:27:47 +0000155 loglevels[packageName] = l
divyadesai8bf96862020-02-07 12:24:26 +0000156 }
divyadesai8bf96862020-02-07 12:24:26 +0000157 }
divyadesai8bf96862020-02-07 12:24:26 +0000158
Girish Kumar21972fb2020-03-13 13:27:47 +0000159 log.Debugw("retreived-log-levels-from-zap-logger", log.Fields{"loglevels": loglevels})
160
161 return loglevels
divyadesai8bf96862020-02-07 12:24:26 +0000162}
163
164func (c *ComponentLogController) getGlobalLogConfig(ctx context.Context) (string, error) {
165
166 globalDefaultLogLevel := ""
167 globalLogConfig, err := c.GlobalConfig.RetrieveAll(ctx)
168 if err != nil {
169 return "", err
170 }
171
Girish Kumar21972fb2020-03-13 13:27:47 +0000172 if globalLevel, ok := globalLogConfig[defaultLogLevelKey]; ok {
divyadesai8bf96862020-02-07 12:24:26 +0000173 if _, err := log.StringToLogLevel(globalLevel); err != nil {
Girish Kumar21972fb2020-03-13 13:27:47 +0000174 log.Warnw("unsupported-loglevel-config-defined-at-global-context-package-name", log.Fields{"log-level": globalLevel})
divyadesai8bf96862020-02-07 12:24:26 +0000175 } else {
176 globalDefaultLogLevel = globalLevel
177 }
178 }
Girish Kumar21972fb2020-03-13 13:27:47 +0000179
divyadesai8bf96862020-02-07 12:24:26 +0000180 log.Debugw("retrieved-global-log-config", log.Fields{"global-log-config": globalLogConfig})
181
182 return globalDefaultLogLevel, nil
183}
184
Girish Kumar21972fb2020-03-13 13:27:47 +0000185func (c *ComponentLogController) getComponentLogConfig(ctx context.Context, globalDefaultLogLevel string) (map[string]string, error) {
divyadesai8bf96862020-02-07 12:24:26 +0000186 componentLogConfig, err := c.componentNameConfig.RetrieveAll(ctx)
187 if err != nil {
188 return nil, err
189 }
190
Girish Kumar21972fb2020-03-13 13:27:47 +0000191 effectiveDefaultLogLevel := ""
192 for logConfigKey, logConfigValue := range componentLogConfig {
193 if _, err := log.StringToLogLevel(logConfigValue); err != nil || logConfigKey == "" {
194 log.Warnw("unsupported-loglevel-config-defined-at-component-context", log.Fields{"package-name": logConfigKey, "log-level": logConfigValue})
195 delete(componentLogConfig, logConfigKey)
divyadesai8bf96862020-02-07 12:24:26 +0000196 } else {
Girish Kumar21972fb2020-03-13 13:27:47 +0000197 if logConfigKey == defaultLogLevelKey {
198 effectiveDefaultLogLevel = componentLogConfig[defaultLogLevelKey]
divyadesai8bf96862020-02-07 12:24:26 +0000199 }
200 }
201 }
Girish Kumar21972fb2020-03-13 13:27:47 +0000202
203 // if default loglevel is not configured for the component, component should use
204 // - default loglevel configured at global level, if set
205 if effectiveDefaultLogLevel == "" {
206 effectiveDefaultLogLevel = globalDefaultLogLevel
divyadesai8bf96862020-02-07 12:24:26 +0000207 }
Girish Kumar21972fb2020-03-13 13:27:47 +0000208 // - else, use initial loglevel which component was started with (set from helm chart)
209 if effectiveDefaultLogLevel == "" {
210 effectiveDefaultLogLevel = c.initialLogLevel
211 }
212
213 componentLogConfig[defaultLogLevelKey] = effectiveDefaultLogLevel
214
divyadesai8bf96862020-02-07 12:24:26 +0000215 log.Debugw("retrieved-component-log-config", log.Fields{"component-log-level": componentLogConfig})
216
217 return componentLogConfig, nil
218}
219
220// buildUpdatedLogConfig retrieve the global logConfig and component logConfig from backend
221// component logConfig stores the log config with precedence order
222// For example, If the global logConfig is set and component logConfig is set only for specific package then
223// component logConfig is stored with global logConfig and component logConfig of specific package
224// For example, If the global logConfig is set and component logConfig is set for specific package and as well as for default then
225// component logConfig is stored with component logConfig data only
226func (c *ComponentLogController) buildUpdatedLogConfig(ctx context.Context) (map[string]string, error) {
227 globalLogLevel, err := c.getGlobalLogConfig(ctx)
228 if err != nil {
229 return nil, err
230 }
231
Girish Kumar21972fb2020-03-13 13:27:47 +0000232 componentLogConfig, err := c.getComponentLogConfig(ctx, globalLogLevel)
divyadesai8bf96862020-02-07 12:24:26 +0000233 if err != nil {
234 return nil, err
235 }
236
Girish Kumar21972fb2020-03-13 13:27:47 +0000237 finalLogConfig := make(map[string]string)
238 for packageName, logLevel := range componentLogConfig {
239 finalLogConfig[strings.ReplaceAll(packageName, "#", "/")] = logLevel
240 }
241
242 return finalLogConfig, nil
divyadesai8bf96862020-02-07 12:24:26 +0000243}
244
245// load and apply the current configuration for component name
246// create hash of loaded configuration using GenerateLogConfigHash
247// if there is previous hash stored, compare the hash to stored hash
248// if there is any change will call UpdateLogLevels
249func (c *ComponentLogController) loadAndApplyLogConfig(logConfig map[string]string) error {
250 currentLogHash, err := GenerateLogConfigHash(logConfig)
251 if err != nil {
252 return err
253 }
254
divyadesai8bf96862020-02-07 12:24:26 +0000255 if c.logHash != currentLogHash {
256 UpdateLogLevels(logConfig)
257 c.logHash = currentLogHash
Girish Kumar21972fb2020-03-13 13:27:47 +0000258 } else {
259 log.Debug("effective-loglevel-config-same-as-currently-active")
divyadesai8bf96862020-02-07 12:24:26 +0000260 }
Girish Kumar21972fb2020-03-13 13:27:47 +0000261
divyadesai8bf96862020-02-07 12:24:26 +0000262 return nil
263}
264
Girish Kumar21972fb2020-03-13 13:27:47 +0000265// createModifiedLogLevels loops through the activeLogLevels recieved from zap logger and updatedLogLevels recieved from buildUpdatedLogConfig
266// to identify and create map of modified Log Levels of 2 types:
267// - Packages for which log level has been changed
268// - Packages for which log level config has been cleared - set to default log level
269func createModifiedLogLevels(activeLogLevels, updatedLogLevels map[string]string) map[string]string {
270 defaultLevel := updatedLogLevels[defaultLogLevelKey]
divyadesai8bf96862020-02-07 12:24:26 +0000271
Girish Kumar21972fb2020-03-13 13:27:47 +0000272 modifiedLogLevels := make(map[string]string)
divyadesai8bf96862020-02-07 12:24:26 +0000273 for activeKey, activeLevel := range activeLogLevels {
274 if _, exist := updatedLogLevels[activeKey]; !exist {
Girish Kumar21972fb2020-03-13 13:27:47 +0000275 if activeLevel != defaultLevel {
276 modifiedLogLevels[activeKey] = defaultLevel
divyadesai8bf96862020-02-07 12:24:26 +0000277 }
Girish Kumar21972fb2020-03-13 13:27:47 +0000278 } else if activeLevel != updatedLogLevels[activeKey] {
279 modifiedLogLevels[activeKey] = updatedLogLevels[activeKey]
divyadesai8bf96862020-02-07 12:24:26 +0000280 }
281 }
Girish Kumar21972fb2020-03-13 13:27:47 +0000282
283 // Log warnings for all invalid packages for which log config has been set
284 for key, value := range updatedLogLevels {
285 if _, exist := activeLogLevels[key]; !exist {
286 log.Warnw("ignoring-loglevel-set-for-invalid-package", log.Fields{"package": key, "log-level": value})
287 }
288 }
289
290 return modifiedLogLevels
divyadesai8bf96862020-02-07 12:24:26 +0000291}
292
293// updateLogLevels update the loglevels for the component
294// retrieve active confguration from logger
295// compare with entries one by one and apply
Girish Kumar21972fb2020-03-13 13:27:47 +0000296func UpdateLogLevels(updatedLogConfig map[string]string) {
divyadesai8bf96862020-02-07 12:24:26 +0000297
Girish Kumar21972fb2020-03-13 13:27:47 +0000298 activeLogLevels := getActiveLogLevels()
299 changedLogLevels := createModifiedLogLevels(activeLogLevels, updatedLogConfig)
300
301 // If no changed log levels are found, just return. It may happen on configuration of a invalid package
302 if len(changedLogLevels) == 0 {
303 log.Debug("no-change-in-effective-loglevel-config")
304 return
305 }
306
307 log.Debugw("applying-log-level-for-modified-packages", log.Fields{"changed-log-levels": changedLogLevels})
308 for key, level := range changedLogLevels {
309 if key == defaultLogLevelKey {
divyadesai8bf96862020-02-07 12:24:26 +0000310 if l, err := log.StringToLogLevel(level); err == nil {
311 log.SetDefaultLogLevel(l)
312 }
313 } else {
divyadesai8bf96862020-02-07 12:24:26 +0000314 if l, err := log.StringToLogLevel(level); err == nil {
Girish Kumar21972fb2020-03-13 13:27:47 +0000315 log.SetPackageLogLevel(key, l)
divyadesai8bf96862020-02-07 12:24:26 +0000316 }
317 }
318 }
divyadesai8bf96862020-02-07 12:24:26 +0000319}
320
321// generate md5 hash of key value pairs appended into a single string
322// in order by key name
323func GenerateLogConfigHash(createHashLog map[string]string) ([16]byte, error) {
324 createHashLogBytes := []byte{}
325 levelData, err := json.Marshal(createHashLog)
326 if err != nil {
327 return [16]byte{}, err
328 }
329 createHashLogBytes = append(createHashLogBytes, levelData...)
330 return md5.Sum(createHashLogBytes), nil
331}