mirror of https://github.com/knative/caching.git
272 lines
7.5 KiB
Go
272 lines
7.5 KiB
Go
/*
|
|
Copyright 2018 The Knative Authors
|
|
|
|
Licensed under the Apache License, Version 2.0 (the "License");
|
|
you may not use this file except in compliance with the License.
|
|
You may obtain a copy of the License at
|
|
|
|
http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
Unless required by applicable law or agreed to in writing, software
|
|
distributed under the License is distributed on an "AS IS" BASIS,
|
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
See the License for the specific language governing permissions and
|
|
limitations under the License.
|
|
*/
|
|
|
|
package logging
|
|
|
|
import (
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"os"
|
|
"strings"
|
|
|
|
"go.uber.org/zap"
|
|
"go.uber.org/zap/zapcore"
|
|
corev1 "k8s.io/api/core/v1"
|
|
|
|
"knative.dev/pkg/changeset"
|
|
"knative.dev/pkg/logging/logkey"
|
|
)
|
|
|
|
const (
|
|
configMapNameEnv = "CONFIG_LOGGING_NAME"
|
|
loggerConfigKey = "zap-logger-config"
|
|
fallbackLoggerName = "fallback-logger"
|
|
)
|
|
|
|
var (
|
|
errEmptyLoggerConfig = errors.New("empty logger configuration")
|
|
errEmptyJSONLogginString = errors.New("json logging string is empty")
|
|
)
|
|
|
|
// NewLogger creates a logger with the supplied configuration.
|
|
// In addition to the logger, it returns AtomicLevel that can
|
|
// be used to change the logging level at runtime.
|
|
// If configuration is empty, a fallback configuration is used.
|
|
// If configuration cannot be used to instantiate a logger,
|
|
// the same fallback configuration is used.
|
|
func NewLogger(configJSON string, levelOverride string, opts ...zap.Option) (*zap.SugaredLogger, zap.AtomicLevel) {
|
|
logger, atomicLevel, err := newLoggerFromConfig(configJSON, levelOverride, opts)
|
|
if err == nil {
|
|
return enrichLoggerWithCommitID(logger), atomicLevel
|
|
}
|
|
|
|
loggingCfg := zap.NewProductionConfig()
|
|
if levelOverride != "" {
|
|
if level, err := levelFromString(levelOverride); err == nil {
|
|
loggingCfg.Level = zap.NewAtomicLevelAt(*level)
|
|
}
|
|
}
|
|
|
|
logger, err2 := loggingCfg.Build(opts...)
|
|
if err2 != nil {
|
|
panic(err2)
|
|
}
|
|
return enrichLoggerWithCommitID(logger.Named(fallbackLoggerName)), loggingCfg.Level
|
|
}
|
|
|
|
func enrichLoggerWithCommitID(logger *zap.Logger) *zap.SugaredLogger {
|
|
commmitID, err := changeset.Get()
|
|
if err == nil {
|
|
// Enrich logs with GitHub commit ID.
|
|
return logger.With(zap.String(logkey.GitHubCommitID, commmitID)).Sugar()
|
|
}
|
|
|
|
logger.Info("Fetch GitHub commit ID from kodata failed", zap.Error(err))
|
|
return logger.Sugar()
|
|
}
|
|
|
|
// NewLoggerFromConfig creates a logger using the provided Config
|
|
func NewLoggerFromConfig(config *Config, name string, opts ...zap.Option) (*zap.SugaredLogger, zap.AtomicLevel) {
|
|
var componentLvl string
|
|
if lvl, defined := config.LoggingLevel[name]; defined {
|
|
componentLvl = lvl.String()
|
|
}
|
|
|
|
logger, level := NewLogger(config.LoggingConfig, componentLvl, opts...)
|
|
return logger.Named(name), level
|
|
}
|
|
|
|
func newLoggerFromConfig(configJSON string, levelOverride string, opts []zap.Option) (*zap.Logger, zap.AtomicLevel, error) {
|
|
loggingCfg, err := zapConfigFromJSON(configJSON)
|
|
if err != nil {
|
|
return nil, zap.AtomicLevel{}, err
|
|
}
|
|
|
|
if levelOverride != "" {
|
|
if level, err := levelFromString(levelOverride); err == nil {
|
|
loggingCfg.Level = zap.NewAtomicLevelAt(*level)
|
|
}
|
|
}
|
|
|
|
logger, err := loggingCfg.Build(opts...)
|
|
if err != nil {
|
|
return nil, zap.AtomicLevel{}, err
|
|
}
|
|
|
|
logger.Info("Successfully created the logger.")
|
|
logger.Info("Logging level set to: " + loggingCfg.Level.String())
|
|
return logger, loggingCfg.Level, nil
|
|
}
|
|
|
|
func zapConfigFromJSON(configJSON string) (*zap.Config, error) {
|
|
if configJSON == "" {
|
|
return nil, errEmptyLoggerConfig
|
|
}
|
|
|
|
loggingCfg := &zap.Config{}
|
|
if err := json.Unmarshal([]byte(configJSON), loggingCfg); err != nil {
|
|
return nil, err
|
|
}
|
|
return loggingCfg, nil
|
|
}
|
|
|
|
// Config contains the configuration defined in the logging ConfigMap.
|
|
// +k8s:deepcopy-gen=true
|
|
type Config struct {
|
|
LoggingConfig string
|
|
LoggingLevel map[string]zapcore.Level
|
|
}
|
|
|
|
const defaultZLC = `{
|
|
"level": "info",
|
|
"development": false,
|
|
"outputPaths": ["stdout"],
|
|
"errorOutputPaths": ["stderr"],
|
|
"encoding": "json",
|
|
"encoderConfig": {
|
|
"timeKey": "ts",
|
|
"levelKey": "level",
|
|
"nameKey": "logger",
|
|
"callerKey": "caller",
|
|
"messageKey": "msg",
|
|
"stacktraceKey": "stacktrace",
|
|
"lineEnding": "",
|
|
"levelEncoder": "",
|
|
"timeEncoder": "iso8601",
|
|
"durationEncoder": "",
|
|
"callerEncoder": ""
|
|
}
|
|
}`
|
|
|
|
func defaultConfig() *Config {
|
|
return &Config{
|
|
LoggingConfig: defaultZLC,
|
|
LoggingLevel: make(map[string]zapcore.Level),
|
|
}
|
|
}
|
|
|
|
// NewConfigFromMap creates a LoggingConfig from the supplied map,
|
|
// expecting the given list of components.
|
|
func NewConfigFromMap(data map[string]string) (*Config, error) {
|
|
lc := defaultConfig()
|
|
if zlc, ok := data[loggerConfigKey]; ok {
|
|
lc.LoggingConfig = zlc
|
|
}
|
|
|
|
for k, v := range data {
|
|
if component := strings.TrimPrefix(k, "loglevel."); component != k && component != "" {
|
|
if len(v) > 0 {
|
|
level, err := levelFromString(v)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
lc.LoggingLevel[component] = *level
|
|
}
|
|
}
|
|
}
|
|
return lc, nil
|
|
}
|
|
|
|
// NewConfigFromConfigMap creates a Config from the supplied ConfigMap,
|
|
// expecting the given list of components.
|
|
func NewConfigFromConfigMap(configMap *corev1.ConfigMap) (*Config, error) {
|
|
return NewConfigFromMap(configMap.Data)
|
|
}
|
|
|
|
func levelFromString(level string) (*zapcore.Level, error) {
|
|
var zapLevel zapcore.Level
|
|
if err := zapLevel.UnmarshalText([]byte(level)); err != nil {
|
|
return nil, fmt.Errorf("invalid logging level: %v", level)
|
|
}
|
|
return &zapLevel, nil
|
|
}
|
|
|
|
// UpdateLevelFromConfigMap returns a helper func that can be used to update the logging level
|
|
// when a config map is updated
|
|
func UpdateLevelFromConfigMap(logger *zap.SugaredLogger, atomicLevel zap.AtomicLevel,
|
|
levelKey string) func(configMap *corev1.ConfigMap) {
|
|
|
|
return func(configMap *corev1.ConfigMap) {
|
|
config, err := NewConfigFromConfigMap(configMap)
|
|
if err != nil {
|
|
logger.Errorw("Failed to parse the logging configmap. Previous config map will be used.", zap.Error(err))
|
|
return
|
|
}
|
|
|
|
level, defined := config.LoggingLevel[levelKey]
|
|
if !defined {
|
|
// reset to global level
|
|
loggingCfg, err := zapConfigFromJSON(config.LoggingConfig)
|
|
switch {
|
|
case err == errEmptyLoggerConfig:
|
|
level = zap.NewAtomicLevel().Level()
|
|
case err != nil:
|
|
logger.With(zap.Error(err)).Errorf("Failed to parse logger configuration. "+
|
|
"Previous log level retained for %v", levelKey)
|
|
return
|
|
default:
|
|
level = loggingCfg.Level.Level()
|
|
}
|
|
}
|
|
|
|
if atomicLevel.Level() != level {
|
|
logger.Infof("Updating logging level for %v from %v to %v.", levelKey, atomicLevel.Level(), level)
|
|
atomicLevel.SetLevel(level)
|
|
}
|
|
}
|
|
}
|
|
|
|
// ConfigMapName gets the name of the logging ConfigMap
|
|
func ConfigMapName() string {
|
|
if cm := os.Getenv(configMapNameEnv); cm != "" {
|
|
return cm
|
|
}
|
|
return "config-logging"
|
|
}
|
|
|
|
// JsonToLoggingConfig converts a json string of a Config.
|
|
// Returns a non-nil Config always.
|
|
func JsonToLoggingConfig(jsonCfg string) (*Config, error) {
|
|
if jsonCfg == "" {
|
|
return nil, errEmptyJSONLogginString
|
|
}
|
|
|
|
var configMap map[string]string
|
|
if err := json.Unmarshal([]byte(jsonCfg), &configMap); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
cfg, err := NewConfigFromMap(configMap)
|
|
if err != nil {
|
|
// Get the default config from logging package.
|
|
return NewConfigFromConfigMap(nil)
|
|
}
|
|
return cfg, nil
|
|
}
|
|
|
|
// LoggingConfigToJson converts a Config to a json string.
|
|
func LoggingConfigToJson(cfg *Config) (string, error) {
|
|
if cfg == nil || cfg.LoggingConfig == "" {
|
|
return "", nil
|
|
}
|
|
|
|
jsonCfg, err := json.Marshal(map[string]string{
|
|
loggerConfigKey: cfg.LoggingConfig,
|
|
})
|
|
return string(jsonCfg), err
|
|
}
|