blob: 65927e627261cfaf1acc31fa38c1d1795166b124 [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
divyadesai52dc0882020-03-19 06:38:11 +000017// Package Config provides dynamic logging configuration for specific Voltha component with loglevel lookup
18// from etcd kvstore implemented using backend.
19// Any Voltha component can start utilizing dynamic logging by starting goroutine of StartLogLevelConfigProcessing after
20// starting kvClient for the component.
divyadesai81bb7ba2020-03-11 11:45:23 +000021
22package config
23
24import (
25 "context"
26 "crypto/md5"
27 "encoding/json"
28 "errors"
29 "github.com/opencord/voltha-lib-go/v3/pkg/log"
30 "os"
31 "strings"
32)
33
divyadesai52dc0882020-03-19 06:38:11 +000034const (
35 defaultLogLevelKey = "default" // kvstore key containing default loglevel
36 globalConfigRootNode = "global" // Root Node in kvstore containing global config
37 initialGlobalDefaultLogLevelValue = "WARN" // Hard-coded Global Default loglevel pushed at PoD startup
38)
39
divyadesai81bb7ba2020-03-11 11:45:23 +000040// ComponentLogController represents a Configuration for Logging Config of specific Voltha component type
41// It stores ComponentConfig and GlobalConfig of loglevel config of specific Voltha component type
42// For example,ComponentLogController instance will be created for rw-core component
43type ComponentLogController struct {
44 ComponentName string
45 componentNameConfig *ComponentConfig
46 GlobalConfig *ComponentConfig
47 configManager *ConfigManager
48 logHash [16]byte
divyadesai52dc0882020-03-19 06:38:11 +000049 initialLogLevel string // Initial default log level set by helm chart
divyadesai81bb7ba2020-03-11 11:45:23 +000050}
51
52func NewComponentLogController(cm *ConfigManager) (*ComponentLogController, error) {
53
54 log.Debug("creating-new-component-log-controller")
55 componentName := os.Getenv("COMPONENT_NAME")
56 if componentName == "" {
57 return nil, errors.New("Unable to retrieve PoD Component Name from Runtime env")
58 }
59
divyadesai52dc0882020-03-19 06:38:11 +000060 var defaultLogLevel string
61 var err error
62 // Retrieve and save default log level; used for fallback if all loglevel config is cleared in etcd
63 if defaultLogLevel, err = log.LogLevelToString(log.GetDefaultLogLevel()); err != nil {
64 defaultLogLevel = "DEBUG"
65 }
66
divyadesai81bb7ba2020-03-11 11:45:23 +000067 return &ComponentLogController{
68 ComponentName: componentName,
69 componentNameConfig: nil,
70 GlobalConfig: nil,
71 configManager: cm,
divyadesai52dc0882020-03-19 06:38:11 +000072 initialLogLevel: defaultLogLevel,
divyadesai81bb7ba2020-03-11 11:45:23 +000073 }, nil
74
75}
76
divyadesai52dc0882020-03-19 06:38:11 +000077// StartLogLevelConfigProcessing initialize component config and global config
78// Then, it persists initial default Loglevels into Config Store before
79// starting the loading and processing of all Log Configuration
80func StartLogLevelConfigProcessing(cm *ConfigManager, ctx context.Context) {
divyadesai81bb7ba2020-03-11 11:45:23 +000081 cc, err := NewComponentLogController(cm)
82 if err != nil {
83 log.Errorw("unable-to-construct-component-log-controller-instance-for-log-config-monitoring", log.Fields{"error": err})
84 return
85 }
86
divyadesai52dc0882020-03-19 06:38:11 +000087 cc.GlobalConfig = cm.InitComponentConfig(globalConfigRootNode, ConfigTypeLogLevel)
divyadesai81bb7ba2020-03-11 11:45:23 +000088 log.Debugw("global-log-config", log.Fields{"cc-global-config": cc.GlobalConfig})
89
90 cc.componentNameConfig = cm.InitComponentConfig(cc.ComponentName, ConfigTypeLogLevel)
91 log.Debugw("component-log-config", log.Fields{"cc-component-name-config": cc.componentNameConfig})
92
divyadesai52dc0882020-03-19 06:38:11 +000093 cc.persistInitialDefaultLogConfigs(ctx)
94
divyadesai81bb7ba2020-03-11 11:45:23 +000095 cc.processLogConfig(ctx)
96}
97
divyadesai52dc0882020-03-19 06:38:11 +000098// Method to persist Global default loglevel into etcd, if not set yet
99// It also checks and set Component default loglevel into etcd with initial loglevel set from command line
100func (c *ComponentLogController) persistInitialDefaultLogConfigs(ctx context.Context) {
101
102 _, err := c.GlobalConfig.Retrieve(ctx, defaultLogLevelKey)
103 if err != nil {
104 log.Debugw("failed-to-retrieve-global-default-log-config-at-startup", log.Fields{"error": err})
105
106 err = c.GlobalConfig.Save(ctx, defaultLogLevelKey, initialGlobalDefaultLogLevelValue)
107 if err != nil {
108 log.Errorw("failed-to-persist-global-default-log-config-at-startup", log.Fields{"error": err, "loglevel": initialGlobalDefaultLogLevelValue})
109 }
110 }
111
112 _, err = c.componentNameConfig.Retrieve(ctx, defaultLogLevelKey)
113 if err != nil {
114 log.Debugw("failed-to-retrieve-component-default-log-config-at-startup", log.Fields{"error": err})
115
116 err = c.componentNameConfig.Save(ctx, defaultLogLevelKey, c.initialLogLevel)
117 if err != nil {
118 log.Errorw("failed-to-persist-component-default-log-config-at-startup", log.Fields{"error": err, "loglevel": c.initialLogLevel})
119 }
120 }
121}
122
123// ProcessLogConfig will first load and apply log config and then start waiting on component config and global config
124// channels for any changes. Event channel will be recieved from backend for valid change type
divyadesai81bb7ba2020-03-11 11:45:23 +0000125// Then data for componentn log config and global log config will be retrieved from backend and stored in updatedLogConfig in precedence order
126// If any changes in updatedLogConfig will be applied on component
127func (c *ComponentLogController) processLogConfig(ctx context.Context) {
128
divyadesai52dc0882020-03-19 06:38:11 +0000129 // Load and apply Log Config for first time
130 initialLogConfig, err := c.buildUpdatedLogConfig(ctx)
131 if err != nil {
132 log.Warnw("unable-to-load-log-config-at-startup", log.Fields{"error": err})
133 } else {
134 if err := c.loadAndApplyLogConfig(initialLogConfig); err != nil {
135 log.Warnw("unable-to-apply-log-config-at-startup", log.Fields{"error": err})
136 }
137 }
138
divyadesai81bb7ba2020-03-11 11:45:23 +0000139 componentConfigEventChan := c.componentNameConfig.MonitorForConfigChange(ctx)
140
141 globalConfigEventChan := c.GlobalConfig.MonitorForConfigChange(ctx)
142
143 // process the events for componentName and global config
144 var configEvent *ConfigChangeEvent
145 for {
146 select {
147 case configEvent = <-globalConfigEventChan:
148 case configEvent = <-componentConfigEventChan:
149
150 }
divyadesai52dc0882020-03-19 06:38:11 +0000151 log.Debugw("processing-log-config-change", log.Fields{"ChangeType": configEvent.ChangeType, "Package": configEvent.ConfigAttribute})
divyadesai81bb7ba2020-03-11 11:45:23 +0000152
153 updatedLogConfig, err := c.buildUpdatedLogConfig(ctx)
154 if err != nil {
155 log.Warnw("unable-to-fetch-updated-log-config", log.Fields{"error": err})
156 continue
157 }
158
159 log.Debugw("applying-updated-log-config", log.Fields{"updated-log-config": updatedLogConfig})
160
161 if err := c.loadAndApplyLogConfig(updatedLogConfig); err != nil {
162 log.Warnw("unable-to-load-and-apply-log-config", log.Fields{"error": err})
163 }
164 }
165
166}
167
168// get active loglevel from the zap logger
divyadesai52dc0882020-03-19 06:38:11 +0000169func getActiveLogLevels() map[string]string {
170 loglevels := make(map[string]string)
divyadesai81bb7ba2020-03-11 11:45:23 +0000171
172 // now do the default log level
173 if level, err := log.LogLevelToString(log.GetDefaultLogLevel()); err == nil {
divyadesai52dc0882020-03-19 06:38:11 +0000174 loglevels[defaultLogLevelKey] = level
divyadesai81bb7ba2020-03-11 11:45:23 +0000175 }
176
177 // do the per-package log levels
178 for _, packageName := range log.GetPackageNames() {
179 level, err := log.GetPackageLogLevel(packageName)
180 if err != nil {
181 log.Warnw("unable-to-fetch-current-active-loglevel-for-package-name", log.Fields{"package-name": packageName, "error": err})
divyadesai52dc0882020-03-19 06:38:11 +0000182 continue
divyadesai81bb7ba2020-03-11 11:45:23 +0000183 }
184
divyadesai81bb7ba2020-03-11 11:45:23 +0000185 if l, err := log.LogLevelToString(level); err == nil {
divyadesai52dc0882020-03-19 06:38:11 +0000186 loglevels[packageName] = l
divyadesai81bb7ba2020-03-11 11:45:23 +0000187 }
divyadesai81bb7ba2020-03-11 11:45:23 +0000188 }
divyadesai81bb7ba2020-03-11 11:45:23 +0000189
divyadesai52dc0882020-03-19 06:38:11 +0000190 log.Debugw("retreived-log-levels-from-zap-logger", log.Fields{"loglevels": loglevels})
191
192 return loglevels
divyadesai81bb7ba2020-03-11 11:45:23 +0000193}
194
195func (c *ComponentLogController) getGlobalLogConfig(ctx context.Context) (string, error) {
196
divyadesai52dc0882020-03-19 06:38:11 +0000197 globalDefaultLogLevel, err := c.GlobalConfig.Retrieve(ctx, defaultLogLevelKey)
divyadesai81bb7ba2020-03-11 11:45:23 +0000198 if err != nil {
199 return "", err
200 }
201
divyadesai52dc0882020-03-19 06:38:11 +0000202 // Handle edge cases when global default loglevel is deleted directly from etcd or set to a invalid value
203 // We should use hard-coded initial default value in such cases
204 if globalDefaultLogLevel == "" {
205 log.Warn("global-default-loglevel-not-found-in-config-store")
206 globalDefaultLogLevel = initialGlobalDefaultLogLevelValue
divyadesai81bb7ba2020-03-11 11:45:23 +0000207 }
divyadesai52dc0882020-03-19 06:38:11 +0000208
209 if _, err := log.StringToLogLevel(globalDefaultLogLevel); err != nil {
210 log.Warnw("unsupported-loglevel-config-defined-at-global-default", log.Fields{"log-level": globalDefaultLogLevel})
211 globalDefaultLogLevel = initialGlobalDefaultLogLevelValue
212 }
213
214 log.Debugw("retrieved-global-default-loglevel", log.Fields{"level": globalDefaultLogLevel})
divyadesai81bb7ba2020-03-11 11:45:23 +0000215
216 return globalDefaultLogLevel, nil
217}
218
divyadesai52dc0882020-03-19 06:38:11 +0000219func (c *ComponentLogController) getComponentLogConfig(ctx context.Context, globalDefaultLogLevel string) (map[string]string, error) {
divyadesai81bb7ba2020-03-11 11:45:23 +0000220 componentLogConfig, err := c.componentNameConfig.RetrieveAll(ctx)
221 if err != nil {
222 return nil, err
223 }
224
divyadesai52dc0882020-03-19 06:38:11 +0000225 effectiveDefaultLogLevel := ""
226 for logConfigKey, logConfigValue := range componentLogConfig {
227 if _, err := log.StringToLogLevel(logConfigValue); err != nil || logConfigKey == "" {
228 log.Warnw("unsupported-loglevel-config-defined-at-component-context", log.Fields{"package-name": logConfigKey, "log-level": logConfigValue})
229 delete(componentLogConfig, logConfigKey)
divyadesai81bb7ba2020-03-11 11:45:23 +0000230 } else {
divyadesai52dc0882020-03-19 06:38:11 +0000231 if logConfigKey == defaultLogLevelKey {
232 effectiveDefaultLogLevel = componentLogConfig[defaultLogLevelKey]
divyadesai81bb7ba2020-03-11 11:45:23 +0000233 }
234 }
235 }
divyadesai52dc0882020-03-19 06:38:11 +0000236
237 // if default loglevel is not configured for the component, component should use
238 // default loglevel configured at global level
239 if effectiveDefaultLogLevel == "" {
240 effectiveDefaultLogLevel = globalDefaultLogLevel
divyadesai81bb7ba2020-03-11 11:45:23 +0000241 }
divyadesai52dc0882020-03-19 06:38:11 +0000242
243 componentLogConfig[defaultLogLevelKey] = effectiveDefaultLogLevel
244
divyadesai81bb7ba2020-03-11 11:45:23 +0000245 log.Debugw("retrieved-component-log-config", log.Fields{"component-log-level": componentLogConfig})
246
247 return componentLogConfig, nil
248}
249
250// buildUpdatedLogConfig retrieve the global logConfig and component logConfig from backend
251// component logConfig stores the log config with precedence order
252// For example, If the global logConfig is set and component logConfig is set only for specific package then
253// component logConfig is stored with global logConfig and component logConfig of specific package
254// For example, If the global logConfig is set and component logConfig is set for specific package and as well as for default then
255// component logConfig is stored with component logConfig data only
256func (c *ComponentLogController) buildUpdatedLogConfig(ctx context.Context) (map[string]string, error) {
257 globalLogLevel, err := c.getGlobalLogConfig(ctx)
258 if err != nil {
divyadesai52dc0882020-03-19 06:38:11 +0000259 log.Errorw("unable-to-retrieve-global-log-config", log.Fields{"err": err})
divyadesai81bb7ba2020-03-11 11:45:23 +0000260 }
261
divyadesai52dc0882020-03-19 06:38:11 +0000262 componentLogConfig, err := c.getComponentLogConfig(ctx, globalLogLevel)
divyadesai81bb7ba2020-03-11 11:45:23 +0000263 if err != nil {
264 return nil, err
265 }
266
divyadesai52dc0882020-03-19 06:38:11 +0000267 finalLogConfig := make(map[string]string)
268 for packageName, logLevel := range componentLogConfig {
269 finalLogConfig[strings.ReplaceAll(packageName, "#", "/")] = logLevel
270 }
271
272 return finalLogConfig, nil
divyadesai81bb7ba2020-03-11 11:45:23 +0000273}
274
275// load and apply the current configuration for component name
276// create hash of loaded configuration using GenerateLogConfigHash
277// if there is previous hash stored, compare the hash to stored hash
278// if there is any change will call UpdateLogLevels
279func (c *ComponentLogController) loadAndApplyLogConfig(logConfig map[string]string) error {
280 currentLogHash, err := GenerateLogConfigHash(logConfig)
281 if err != nil {
282 return err
283 }
284
divyadesai81bb7ba2020-03-11 11:45:23 +0000285 if c.logHash != currentLogHash {
286 UpdateLogLevels(logConfig)
287 c.logHash = currentLogHash
divyadesai52dc0882020-03-19 06:38:11 +0000288 } else {
289 log.Debug("effective-loglevel-config-same-as-currently-active")
divyadesai81bb7ba2020-03-11 11:45:23 +0000290 }
divyadesai52dc0882020-03-19 06:38:11 +0000291
divyadesai81bb7ba2020-03-11 11:45:23 +0000292 return nil
293}
294
divyadesai52dc0882020-03-19 06:38:11 +0000295// createModifiedLogLevels loops through the activeLogLevels recieved from zap logger and updatedLogLevels recieved from buildUpdatedLogConfig
296// to identify and create map of modified Log Levels of 2 types:
297// - Packages for which log level has been changed
298// - Packages for which log level config has been cleared - set to default log level
299func createModifiedLogLevels(activeLogLevels, updatedLogLevels map[string]string) map[string]string {
300 defaultLevel := updatedLogLevels[defaultLogLevelKey]
divyadesai81bb7ba2020-03-11 11:45:23 +0000301
divyadesai52dc0882020-03-19 06:38:11 +0000302 modifiedLogLevels := make(map[string]string)
divyadesai81bb7ba2020-03-11 11:45:23 +0000303 for activeKey, activeLevel := range activeLogLevels {
304 if _, exist := updatedLogLevels[activeKey]; !exist {
divyadesai52dc0882020-03-19 06:38:11 +0000305 if activeLevel != defaultLevel {
306 modifiedLogLevels[activeKey] = defaultLevel
divyadesai81bb7ba2020-03-11 11:45:23 +0000307 }
divyadesai52dc0882020-03-19 06:38:11 +0000308 } else if activeLevel != updatedLogLevels[activeKey] {
309 modifiedLogLevels[activeKey] = updatedLogLevels[activeKey]
divyadesai81bb7ba2020-03-11 11:45:23 +0000310 }
311 }
divyadesai52dc0882020-03-19 06:38:11 +0000312
313 // Log warnings for all invalid packages for which log config has been set
314 for key, value := range updatedLogLevels {
315 if _, exist := activeLogLevels[key]; !exist {
316 log.Warnw("ignoring-loglevel-set-for-invalid-package", log.Fields{"package": key, "log-level": value})
317 }
318 }
319
320 return modifiedLogLevels
divyadesai81bb7ba2020-03-11 11:45:23 +0000321}
322
323// updateLogLevels update the loglevels for the component
324// retrieve active confguration from logger
325// compare with entries one by one and apply
divyadesai52dc0882020-03-19 06:38:11 +0000326func UpdateLogLevels(updatedLogConfig map[string]string) {
divyadesai81bb7ba2020-03-11 11:45:23 +0000327
divyadesai52dc0882020-03-19 06:38:11 +0000328 activeLogLevels := getActiveLogLevels()
329 changedLogLevels := createModifiedLogLevels(activeLogLevels, updatedLogConfig)
330
331 // If no changed log levels are found, just return. It may happen on configuration of a invalid package
332 if len(changedLogLevels) == 0 {
333 log.Debug("no-change-in-effective-loglevel-config")
334 return
335 }
336
337 log.Debugw("applying-log-level-for-modified-packages", log.Fields{"changed-log-levels": changedLogLevels})
338 for key, level := range changedLogLevels {
339 if key == defaultLogLevelKey {
divyadesai81bb7ba2020-03-11 11:45:23 +0000340 if l, err := log.StringToLogLevel(level); err == nil {
341 log.SetDefaultLogLevel(l)
342 }
343 } else {
divyadesai81bb7ba2020-03-11 11:45:23 +0000344 if l, err := log.StringToLogLevel(level); err == nil {
divyadesai52dc0882020-03-19 06:38:11 +0000345 log.SetPackageLogLevel(key, l)
divyadesai81bb7ba2020-03-11 11:45:23 +0000346 }
347 }
348 }
divyadesai81bb7ba2020-03-11 11:45:23 +0000349}
350
351// generate md5 hash of key value pairs appended into a single string
352// in order by key name
353func GenerateLogConfigHash(createHashLog map[string]string) ([16]byte, error) {
354 createHashLogBytes := []byte{}
355 levelData, err := json.Marshal(createHashLog)
356 if err != nil {
357 return [16]byte{}, err
358 }
359 createHashLogBytes = append(createHashLogBytes, levelData...)
360 return md5.Sum(createHashLogBytes), nil
361}