blob: 68bfb3295d3e0da80ac0f5b2425b8f4a373ab4d8 [file] [log] [blame]
Holger Hildebrandt0f9b88d2020-04-20 13:33:25 +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 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.
21
22package config
23
24import (
25 "context"
26 "crypto/md5"
27 "encoding/json"
28 "errors"
Girish Gowdra50e56422021-06-01 16:46:04 -070029 "github.com/opencord/voltha-lib-go/v5/pkg/log"
Holger Hildebrandt0f9b88d2020-04-20 13:33:25 +000030 "os"
Matteo Scandolod132c0e2020-04-24 17:06:25 -070031 "sort"
Holger Hildebrandt0f9b88d2020-04-20 13:33:25 +000032 "strings"
33)
34
35const (
Matteo Scandolod132c0e2020-04-24 17:06:25 -070036 defaultLogLevelKey = "default" // kvstore key containing default loglevel
37 globalConfigRootNode = "global" // Root Node in kvstore containing global config
38 initialGlobalDefaultLogLevelValue = "WARN" // Hard-coded Global Default loglevel pushed at PoD startup
39 logPackagesListKey = "log_package_list" // kvstore key containing list of allowed log packages
Holger Hildebrandt0f9b88d2020-04-20 13:33:25 +000040)
41
42// ComponentLogController represents a Configuration for Logging Config of specific Voltha component type
43// It stores ComponentConfig and GlobalConfig of loglevel config of specific Voltha component type
44// For example,ComponentLogController instance will be created for rw-core component
45type ComponentLogController struct {
46 ComponentName string
47 componentNameConfig *ComponentConfig
48 GlobalConfig *ComponentConfig
49 configManager *ConfigManager
50 logHash [16]byte
51 initialLogLevel string // Initial default log level set by helm chart
52}
53
dbainbri4d3a0dc2020-12-02 00:33:42 +000054func NewComponentLogController(ctx context.Context, cm *ConfigManager) (*ComponentLogController, error) {
55 logger.Debug(ctx, "creating-new-component-log-controller")
Holger Hildebrandt0f9b88d2020-04-20 13:33:25 +000056 componentName := os.Getenv("COMPONENT_NAME")
57 if componentName == "" {
58 return nil, errors.New("Unable to retrieve PoD Component Name from Runtime env")
59 }
60
61 var defaultLogLevel string
62 var err error
63 // Retrieve and save default log level; used for fallback if all loglevel config is cleared in etcd
64 if defaultLogLevel, err = log.LogLevelToString(log.GetDefaultLogLevel()); err != nil {
65 defaultLogLevel = "DEBUG"
66 }
67
68 return &ComponentLogController{
69 ComponentName: componentName,
70 componentNameConfig: nil,
71 GlobalConfig: nil,
72 configManager: cm,
73 initialLogLevel: defaultLogLevel,
74 }, nil
75
76}
77
78// StartLogLevelConfigProcessing initialize component config and global config
79// Then, it persists initial default Loglevels into Config Store before
80// starting the loading and processing of all Log Configuration
81func StartLogLevelConfigProcessing(cm *ConfigManager, ctx context.Context) {
dbainbri4d3a0dc2020-12-02 00:33:42 +000082 cc, err := NewComponentLogController(ctx, cm)
Holger Hildebrandt0f9b88d2020-04-20 13:33:25 +000083 if err != nil {
dbainbri4d3a0dc2020-12-02 00:33:42 +000084 logger.Errorw(ctx, "unable-to-construct-component-log-controller-instance-for-log-config-monitoring", log.Fields{"error": err})
Holger Hildebrandt0f9b88d2020-04-20 13:33:25 +000085 return
86 }
87
88 cc.GlobalConfig = cm.InitComponentConfig(globalConfigRootNode, ConfigTypeLogLevel)
dbainbri4d3a0dc2020-12-02 00:33:42 +000089 logger.Debugw(ctx, "global-log-config", log.Fields{"cc-global-config": cc.GlobalConfig})
Holger Hildebrandt0f9b88d2020-04-20 13:33:25 +000090
91 cc.componentNameConfig = cm.InitComponentConfig(cc.ComponentName, ConfigTypeLogLevel)
dbainbri4d3a0dc2020-12-02 00:33:42 +000092 logger.Debugw(ctx, "component-log-config", log.Fields{"cc-component-name-config": cc.componentNameConfig})
Holger Hildebrandt0f9b88d2020-04-20 13:33:25 +000093
94 cc.persistInitialDefaultLogConfigs(ctx)
95
Matteo Scandolod132c0e2020-04-24 17:06:25 -070096 cc.persistRegisteredLogPackageList(ctx)
97
Holger Hildebrandt0f9b88d2020-04-20 13:33:25 +000098 cc.processLogConfig(ctx)
99}
100
101// Method to persist Global default loglevel into etcd, if not set yet
102// It also checks and set Component default loglevel into etcd with initial loglevel set from command line
103func (c *ComponentLogController) persistInitialDefaultLogConfigs(ctx context.Context) {
104
105 _, err := c.GlobalConfig.Retrieve(ctx, defaultLogLevelKey)
106 if err != nil {
dbainbri4d3a0dc2020-12-02 00:33:42 +0000107 logger.Debugw(ctx, "failed-to-retrieve-global-default-log-config-at-startup", log.Fields{"error": err})
Holger Hildebrandt0f9b88d2020-04-20 13:33:25 +0000108
109 err = c.GlobalConfig.Save(ctx, defaultLogLevelKey, initialGlobalDefaultLogLevelValue)
110 if err != nil {
dbainbri4d3a0dc2020-12-02 00:33:42 +0000111 logger.Errorw(ctx, "failed-to-persist-global-default-log-config-at-startup", log.Fields{"error": err, "loglevel": initialGlobalDefaultLogLevelValue})
Holger Hildebrandt0f9b88d2020-04-20 13:33:25 +0000112 }
113 }
114
115 _, err = c.componentNameConfig.Retrieve(ctx, defaultLogLevelKey)
116 if err != nil {
dbainbri4d3a0dc2020-12-02 00:33:42 +0000117 logger.Debugw(ctx, "failed-to-retrieve-component-default-log-config-at-startup", log.Fields{"error": err})
Holger Hildebrandt0f9b88d2020-04-20 13:33:25 +0000118
119 err = c.componentNameConfig.Save(ctx, defaultLogLevelKey, c.initialLogLevel)
120 if err != nil {
dbainbri4d3a0dc2020-12-02 00:33:42 +0000121 logger.Errorw(ctx, "failed-to-persist-component-default-log-config-at-startup", log.Fields{"error": err, "loglevel": c.initialLogLevel})
Holger Hildebrandt0f9b88d2020-04-20 13:33:25 +0000122 }
123 }
124}
125
Matteo Scandolod132c0e2020-04-24 17:06:25 -0700126// Method to save list of all registered packages for component into config kvstore. A single string
127// is constructed with comma-separated package names in sorted order and persisted
128func (c *ComponentLogController) persistRegisteredLogPackageList(ctx context.Context) {
129
130 componentMetadataConfig := c.configManager.InitComponentConfig(c.ComponentName, ConfigTypeMetadata)
dbainbri4d3a0dc2020-12-02 00:33:42 +0000131 logger.Debugw(ctx, "component-metadata-config", log.Fields{"component-metadata-config": componentMetadataConfig})
Matteo Scandolod132c0e2020-04-24 17:06:25 -0700132
133 packageList := log.GetPackageNames()
134 packageList = append(packageList, defaultLogLevelKey)
135 sort.Strings(packageList)
136
137 packageNames, err := json.Marshal(packageList)
138 if err != nil {
dbainbri4d3a0dc2020-12-02 00:33:42 +0000139 logger.Errorw(ctx, "failed-to-marshal-log-package-list-for-storage", log.Fields{"error": err, "packageList": packageList})
Matteo Scandolod132c0e2020-04-24 17:06:25 -0700140 return
141 }
142
143 if err := componentMetadataConfig.Save(ctx, logPackagesListKey, string(packageNames)); err != nil {
dbainbri4d3a0dc2020-12-02 00:33:42 +0000144 logger.Errorw(ctx, "failed-to-persist-component-registered-log-package-list-at-startup", log.Fields{"error": err, "packageNames": packageNames})
Matteo Scandolod132c0e2020-04-24 17:06:25 -0700145 }
146}
147
Holger Hildebrandt0f9b88d2020-04-20 13:33:25 +0000148// ProcessLogConfig will first load and apply log config and then start waiting on component config and global config
149// channels for any changes. Event channel will be recieved from Backend for valid change type
150// Then data for componentn log config and global log config will be retrieved from Backend and stored in updatedLogConfig in precedence order
151// If any changes in updatedLogConfig will be applied on component
152func (c *ComponentLogController) processLogConfig(ctx context.Context) {
153
154 // Load and apply Log Config for first time
155 initialLogConfig, err := c.buildUpdatedLogConfig(ctx)
156 if err != nil {
dbainbri4d3a0dc2020-12-02 00:33:42 +0000157 logger.Warnw(ctx, "unable-to-load-log-config-at-startup", log.Fields{"error": err})
Holger Hildebrandt0f9b88d2020-04-20 13:33:25 +0000158 } else {
dbainbri4d3a0dc2020-12-02 00:33:42 +0000159 if err := c.loadAndApplyLogConfig(ctx, initialLogConfig); err != nil {
160 logger.Warnw(ctx, "unable-to-apply-log-config-at-startup", log.Fields{"error": err})
Holger Hildebrandt0f9b88d2020-04-20 13:33:25 +0000161 }
162 }
163
164 componentConfigEventChan := c.componentNameConfig.MonitorForConfigChange(ctx)
165
166 globalConfigEventChan := c.GlobalConfig.MonitorForConfigChange(ctx)
167
168 // process the events for componentName and global config
169 var configEvent *ConfigChangeEvent
170 for {
171 select {
172 case configEvent = <-globalConfigEventChan:
173 case configEvent = <-componentConfigEventChan:
174
175 }
dbainbri4d3a0dc2020-12-02 00:33:42 +0000176 logger.Debugw(ctx, "processing-log-config-change", log.Fields{"ChangeType": configEvent.ChangeType, "Package": configEvent.ConfigAttribute})
Holger Hildebrandt0f9b88d2020-04-20 13:33:25 +0000177
178 updatedLogConfig, err := c.buildUpdatedLogConfig(ctx)
179 if err != nil {
dbainbri4d3a0dc2020-12-02 00:33:42 +0000180 logger.Warnw(ctx, "unable-to-fetch-updated-log-config", log.Fields{"error": err})
Holger Hildebrandt0f9b88d2020-04-20 13:33:25 +0000181 continue
182 }
183
dbainbri4d3a0dc2020-12-02 00:33:42 +0000184 logger.Debugw(ctx, "applying-updated-log-config", log.Fields{"updated-log-config": updatedLogConfig})
Holger Hildebrandt0f9b88d2020-04-20 13:33:25 +0000185
dbainbri4d3a0dc2020-12-02 00:33:42 +0000186 if err := c.loadAndApplyLogConfig(ctx, updatedLogConfig); err != nil {
187 logger.Warnw(ctx, "unable-to-load-and-apply-log-config", log.Fields{"error": err})
Holger Hildebrandt0f9b88d2020-04-20 13:33:25 +0000188 }
189 }
190
191}
192
193// get active loglevel from the zap logger
dbainbri4d3a0dc2020-12-02 00:33:42 +0000194func getActiveLogLevels(ctx context.Context) map[string]string {
Holger Hildebrandt0f9b88d2020-04-20 13:33:25 +0000195 loglevels := make(map[string]string)
196
197 // now do the default log level
198 if level, err := log.LogLevelToString(log.GetDefaultLogLevel()); err == nil {
199 loglevels[defaultLogLevelKey] = level
200 }
201
202 // do the per-package log levels
203 for _, packageName := range log.GetPackageNames() {
204 level, err := log.GetPackageLogLevel(packageName)
205 if err != nil {
dbainbri4d3a0dc2020-12-02 00:33:42 +0000206 logger.Warnw(ctx, "unable-to-fetch-current-active-loglevel-for-package-name", log.Fields{"package-name": packageName, "error": err})
Holger Hildebrandt0f9b88d2020-04-20 13:33:25 +0000207 continue
208 }
209
210 if l, err := log.LogLevelToString(level); err == nil {
211 loglevels[packageName] = l
212 }
213 }
214
dbainbri4d3a0dc2020-12-02 00:33:42 +0000215 logger.Debugw(ctx, "retreived-log-levels-from-zap-logger", log.Fields{"loglevels": loglevels})
Holger Hildebrandt0f9b88d2020-04-20 13:33:25 +0000216
217 return loglevels
218}
219
220func (c *ComponentLogController) getGlobalLogConfig(ctx context.Context) (string, error) {
221
222 globalDefaultLogLevel, err := c.GlobalConfig.Retrieve(ctx, defaultLogLevelKey)
223 if err != nil {
224 return "", err
225 }
226
227 // Handle edge cases when global default loglevel is deleted directly from etcd or set to a invalid value
228 // We should use hard-coded initial default value in such cases
229 if globalDefaultLogLevel == "" {
dbainbri4d3a0dc2020-12-02 00:33:42 +0000230 logger.Warn(ctx, "global-default-loglevel-not-found-in-config-store")
Holger Hildebrandt0f9b88d2020-04-20 13:33:25 +0000231 globalDefaultLogLevel = initialGlobalDefaultLogLevelValue
232 }
233
234 if _, err := log.StringToLogLevel(globalDefaultLogLevel); err != nil {
dbainbri4d3a0dc2020-12-02 00:33:42 +0000235 logger.Warnw(ctx, "unsupported-loglevel-config-defined-at-global-default", log.Fields{"log-level": globalDefaultLogLevel})
Holger Hildebrandt0f9b88d2020-04-20 13:33:25 +0000236 globalDefaultLogLevel = initialGlobalDefaultLogLevelValue
237 }
238
dbainbri4d3a0dc2020-12-02 00:33:42 +0000239 logger.Debugw(ctx, "retrieved-global-default-loglevel", log.Fields{"level": globalDefaultLogLevel})
Holger Hildebrandt0f9b88d2020-04-20 13:33:25 +0000240
241 return globalDefaultLogLevel, nil
242}
243
244func (c *ComponentLogController) getComponentLogConfig(ctx context.Context, globalDefaultLogLevel string) (map[string]string, error) {
245 componentLogConfig, err := c.componentNameConfig.RetrieveAll(ctx)
246 if err != nil {
247 return nil, err
248 }
249
250 effectiveDefaultLogLevel := ""
251 for logConfigKey, logConfigValue := range componentLogConfig {
252 if _, err := log.StringToLogLevel(logConfigValue); err != nil || logConfigKey == "" {
dbainbri4d3a0dc2020-12-02 00:33:42 +0000253 logger.Warnw(ctx, "unsupported-loglevel-config-defined-at-component-context", log.Fields{"package-name": logConfigKey, "log-level": logConfigValue})
Holger Hildebrandt0f9b88d2020-04-20 13:33:25 +0000254 delete(componentLogConfig, logConfigKey)
255 } else {
256 if logConfigKey == defaultLogLevelKey {
257 effectiveDefaultLogLevel = componentLogConfig[defaultLogLevelKey]
258 }
259 }
260 }
261
262 // if default loglevel is not configured for the component, component should use
263 // default loglevel configured at global level
264 if effectiveDefaultLogLevel == "" {
265 effectiveDefaultLogLevel = globalDefaultLogLevel
266 }
267
268 componentLogConfig[defaultLogLevelKey] = effectiveDefaultLogLevel
269
dbainbri4d3a0dc2020-12-02 00:33:42 +0000270 logger.Debugw(ctx, "retrieved-component-log-config", log.Fields{"component-log-level": componentLogConfig})
Holger Hildebrandt0f9b88d2020-04-20 13:33:25 +0000271
272 return componentLogConfig, nil
273}
274
275// buildUpdatedLogConfig retrieve the global logConfig and component logConfig from Backend
276// component logConfig stores the log config with precedence order
277// For example, If the global logConfig is set and component logConfig is set only for specific package then
278// component logConfig is stored with global logConfig and component logConfig of specific package
279// For example, If the global logConfig is set and component logConfig is set for specific package and as well as for default then
280// component logConfig is stored with component logConfig data only
281func (c *ComponentLogController) buildUpdatedLogConfig(ctx context.Context) (map[string]string, error) {
282 globalLogLevel, err := c.getGlobalLogConfig(ctx)
283 if err != nil {
dbainbri4d3a0dc2020-12-02 00:33:42 +0000284 logger.Errorw(ctx, "unable-to-retrieve-global-log-config", log.Fields{"err": err})
Holger Hildebrandt0f9b88d2020-04-20 13:33:25 +0000285 }
286
287 componentLogConfig, err := c.getComponentLogConfig(ctx, globalLogLevel)
288 if err != nil {
289 return nil, err
290 }
291
292 finalLogConfig := make(map[string]string)
293 for packageName, logLevel := range componentLogConfig {
294 finalLogConfig[strings.ReplaceAll(packageName, "#", "/")] = logLevel
295 }
296
297 return finalLogConfig, nil
298}
299
300// load and apply the current configuration for component name
301// create hash of loaded configuration using GenerateLogConfigHash
302// if there is previous hash stored, compare the hash to stored hash
303// if there is any change will call UpdateLogLevels
dbainbri4d3a0dc2020-12-02 00:33:42 +0000304func (c *ComponentLogController) loadAndApplyLogConfig(ctx context.Context, logConfig map[string]string) error {
Holger Hildebrandt0f9b88d2020-04-20 13:33:25 +0000305 currentLogHash, err := GenerateLogConfigHash(logConfig)
306 if err != nil {
307 return err
308 }
309
310 if c.logHash != currentLogHash {
dbainbri4d3a0dc2020-12-02 00:33:42 +0000311 updateLogLevels(ctx, logConfig)
Holger Hildebrandt0f9b88d2020-04-20 13:33:25 +0000312 c.logHash = currentLogHash
313 } else {
dbainbri4d3a0dc2020-12-02 00:33:42 +0000314 logger.Debug(ctx, "effective-loglevel-config-same-as-currently-active")
Holger Hildebrandt0f9b88d2020-04-20 13:33:25 +0000315 }
316
317 return nil
318}
319
320// createModifiedLogLevels loops through the activeLogLevels recieved from zap logger and updatedLogLevels recieved from buildUpdatedLogConfig
321// to identify and create map of modified Log Levels of 2 types:
322// - Packages for which log level has been changed
323// - Packages for which log level config has been cleared - set to default log level
dbainbri4d3a0dc2020-12-02 00:33:42 +0000324func createModifiedLogLevels(ctx context.Context, activeLogLevels, updatedLogLevels map[string]string) map[string]string {
Holger Hildebrandt0f9b88d2020-04-20 13:33:25 +0000325 defaultLevel := updatedLogLevels[defaultLogLevelKey]
326
327 modifiedLogLevels := make(map[string]string)
328 for activeKey, activeLevel := range activeLogLevels {
329 if _, exist := updatedLogLevels[activeKey]; !exist {
330 if activeLevel != defaultLevel {
331 modifiedLogLevels[activeKey] = defaultLevel
332 }
333 } else if activeLevel != updatedLogLevels[activeKey] {
334 modifiedLogLevels[activeKey] = updatedLogLevels[activeKey]
335 }
336 }
337
338 // Log warnings for all invalid packages for which log config has been set
339 for key, value := range updatedLogLevels {
340 if _, exist := activeLogLevels[key]; !exist {
dbainbri4d3a0dc2020-12-02 00:33:42 +0000341 logger.Warnw(ctx, "ignoring-loglevel-set-for-invalid-package", log.Fields{"package": key, "log-level": value})
Holger Hildebrandt0f9b88d2020-04-20 13:33:25 +0000342 }
343 }
344
345 return modifiedLogLevels
346}
347
348// updateLogLevels update the loglevels for the component
349// retrieve active confguration from logger
350// compare with entries one by one and apply
dbainbri4d3a0dc2020-12-02 00:33:42 +0000351func updateLogLevels(ctx context.Context, updatedLogConfig map[string]string) {
Holger Hildebrandt0f9b88d2020-04-20 13:33:25 +0000352
dbainbri4d3a0dc2020-12-02 00:33:42 +0000353 activeLogLevels := getActiveLogLevels(ctx)
354 changedLogLevels := createModifiedLogLevels(ctx, activeLogLevels, updatedLogConfig)
Holger Hildebrandt0f9b88d2020-04-20 13:33:25 +0000355
356 // If no changed log levels are found, just return. It may happen on configuration of a invalid package
357 if len(changedLogLevels) == 0 {
dbainbri4d3a0dc2020-12-02 00:33:42 +0000358 logger.Debug(ctx, "no-change-in-effective-loglevel-config")
Holger Hildebrandt0f9b88d2020-04-20 13:33:25 +0000359 return
360 }
361
dbainbri4d3a0dc2020-12-02 00:33:42 +0000362 logger.Debugw(ctx, "applying-log-level-for-modified-packages", log.Fields{"changed-log-levels": changedLogLevels})
Holger Hildebrandt0f9b88d2020-04-20 13:33:25 +0000363 for key, level := range changedLogLevels {
364 if key == defaultLogLevelKey {
365 if l, err := log.StringToLogLevel(level); err == nil {
366 log.SetDefaultLogLevel(l)
367 }
368 } else {
369 if l, err := log.StringToLogLevel(level); err == nil {
370 log.SetPackageLogLevel(key, l)
371 }
372 }
373 }
374}
375
376// generate md5 hash of key value pairs appended into a single string
377// in order by key name
378func GenerateLogConfigHash(createHashLog map[string]string) ([16]byte, error) {
379 createHashLogBytes := []byte{}
380 levelData, err := json.Marshal(createHashLog)
381 if err != nil {
382 return [16]byte{}, err
383 }
384 createHashLogBytes = append(createHashLogBytes, levelData...)
385 return md5.Sum(createHashLogBytes), nil
386}